{"version":3,"file":"chunk-chch1t8q.js","sources":["node_modules/fastdom/fastdom.js","packages/sports/libs/modal/feature/provider/src/modal-window-config.token.ts","packages/sports/libs/modal/feature/src/dialog/modal-animation-helper.service.ts","packages/sports/libs/modal/feature/src/dialog/opened-modals-storage.service.ts","packages/sports/libs/modal/feature/src/dialog/modal-gesture.service.ts","packages/sports/libs/modal/feature/src/dialog/modal-horizontal-gesture.service.ts","packages/sports/libs/modal/feature/src/common/actions.ts","packages/sports/libs/modal/feature/src/common/modal-dismiss-reasons.ts","packages/sports/libs/modal/feature/src/common/modal-window.html","packages/sports/libs/modal/feature/src/common/modal-window.component.ts","packages/sports/libs/modal/feature/src/dialog/modal-dialog.model.ts","packages/sports/libs/modal/feature/src/common/modal-backdrop.component.ts","packages/sports/libs/modal/feature/src/common/modal-cover.component.ts","packages/sports/libs/modal/feature/src/common/modal-stack.ts","packages/sports/libs/modal/feature/src/common/modal.ts","packages/sports/libs/modal/feature/src/dialog/message-dialog.html","packages/sports/libs/modal/feature/src/dialog/message-dialog.component.ts","packages/sports/libs/modal/feature/src/dialog/modal-dialog.component.ts","packages/sports/libs/modal/feature/src/dialog/modal-dialog.html","packages/sports/libs/modal/feature/src/dialog/modal-event-bridge.service.ts","packages/sports/libs/modal/feature/src/dialog/modal-dialog.service.ts","packages/sports/web/app/src/common/epcot-config.service.ts","packages/vanilla/lib/shared/header/header-search.service.ts","packages/sports/web/app/src/racing/models/events.ts","packages/sports/web/app/src/popup/sensitive-page-tracking.service.ts","packages/sports/web/app/src/popup/popup-manager.service.ts","packages/sports/common/betslip/core/picks/pick-models.ts","packages/sports/common/betslip/core/picks/pick-id.ts","packages/sports/common/betslip/core/betslip-state.ts","packages/sports/common/betslip/base/store/state.ts","packages/sports/common/betslip/base/store/selectors.ts","packages/sports/libs/odds/feature/src/odds/fraction.ts","packages/sports/libs/odds/feature/src/odds/odds.ts","packages/sports/libs/odds/feature/src/odds/odds-converter.ts","packages/sports/libs/odds/feature/src/odds/operations.ts","packages/sports/common/betslip/core/picks/betslip-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-option-market-pick.ts","packages/sports/common/betslip/core/picks/betslip-bet-builder-pick.ts","packages/sports/common/betslip/modules/validation/picks/betslip-pick-change-info.ts","packages/sports/common/betslip/core/picks/combo-prevention.ts","packages/sports/common/betslip/core/picks/minimum-combination.ts","packages/sports/common/betslip/core/picks/betslip-v1-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-standard-pick.ts","packages/sports/common/betslip/core/price.ts","packages/sports/common/betslip/core/picks/betslip-v2-participant-pick.ts","packages/sports/common/betslip/core/picks/betslip-v2-win-participant-pick.ts","packages/sports/common/betslip/core/picks/sport-specific/betslip-v2-golf-picks.ts","packages/sports/common/betslip/core/picks/sport-specific/betslip-v2-horse-race-picks.ts","packages/sports/common/betslip/modules/picks/services/betslip-pick-storage-loader.ts","packages/sports/common/betslip/core/group-pick-helpers.ts","packages/sports/common/betslip/core/picks/group-leg-state.ts","packages/sports/common/betslip/core/picks/betslip-group-pick.ts","packages/sports/common/betslip/core/picks/betslip-unknown-pick.ts","packages/sports/common/betslip/core/utils.ts","packages/sports/common/betslip/core/betslip-type.ts","packages/sports/common/betslip/model/over-ask/over-ask.ts","packages/sports/common/betslip/modules/picks/services/betslip-pick-helpers.ts","packages/sports/common/betslip/modules/picks/selectors.ts","packages/sports/common/betslip/modules/types/selectors.ts","packages/sports/common/betslip/modules/validation/state.ts","packages/sports/web/app/src/store-persist/storage-persister.ts","packages/sports/common/betslip/core/external-betslip-actions.ts","packages/sports/common/betslip/modules/types/base/selectors.ts","packages/sports/common/betslip/modules/validation/errors/bet-placement-error-icon.ts","packages/sports/common/betslip/modules/validation/errors/betslip-error.ts","packages/sports/common/betslip/modules/validation/errors/bet-placement-error.ts","packages/sports/common/betslip/modules/validation/errors/user/user-error.ts","packages/sports/common/betslip/modules/validation/errors/stake-error.ts","packages/sports/common/betslip/modules/validation/errors/general/under-minimum-stake.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/pre-check-error.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/under-minimum-stake-pre-check-error.ts","packages/sports/common/betslip/modules/stake/utils.ts","packages/sports/common/betslip/modules/single-bet/selectors.ts","packages/sports/common/betslip/modules/bet-generation/betslip-system-type.ts","packages/sports/common/betslip/modules/system-bet/models.ts","packages/sports/common/betslip/modules/bet-generation/slip-type.ts","packages/sports/common/betslip/modules/bet-generation/services/slip.utils.ts","packages/sports/common/betslip/modules/linear-bet-builder/selectors.ts","packages/sports/common/betslip/modules/stake/selectors.ts","packages/sports/common/betslip/modules/system-bet/selectors.ts","packages/sports/common/betslip/modules/validation/errors/general/min-selections-betbuilder-error.ts","packages/sports/common/betslip/modules/validation/errors/result/result-error.ts","packages/sports/common/betslip/modules/validation/services/utils/betslip-errors-state-utils.ts","packages/sports/common/betslip/modules/reward-tokens/reward-tokens.model.ts","packages/sports/common/betslip/modules/reward-tokens/services/reward-tokens.utils.ts","packages/sports/common/betslip/modules/system-bet/services/system-bet.constants.ts","packages/sports/common/betslip/modules/betplacement/services/stake/stake-utils.ts","packages/sports/common/betslip/modules/betplacement/services/stake/linear-stake-utils.ts","packages/sports/common/betslip/modules/picks/services/linear-betslip-pick.utils.ts","packages/sports/common/betslip/modules/validation/errors/client-error-type.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-bet-builder-error.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-min-stake-error.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-selection-level-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/betandget/reward-tokens-total-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/group-pick-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-forbidden-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-game-type-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-max-stake-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-max-total-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-min-legs-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-selection-level-max-odds-error.ts","packages/sports/common/betslip/modules/validation/errors/general/reward-tokens-slip-type-error.ts","packages/sports/common/betslip/modules/validation/errors/general/technical-error.ts","packages/sports/common/betslip/modules/validation/errors/general/incorrect-bet-count.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/incorrect-options-count-pre-check-error.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/no-betbuilder-system-pre-check-error.ts","packages/sports/common/betslip/modules/validation/errors/user/not-enough-money.ts","packages/sports/common/betslip/modules/validation/errors/user/overall-max-win-per-user.ts","packages/sports/common/betslip/modules/validation/services/utils/stake-limit-errors-info-utils.ts","packages/sports/common/betslip/modules/validation/services/utils/betslip-errors-utils.ts","packages/sports/common/betslip/modules/validation/selectors.ts","packages/sports/common/betslip/modules/reward-tokens/services/linear-reward-tokens.utils.ts","packages/sports/common/betslip/modules/reward-tokens/services/betstation-reward-tokens.utils.ts","packages/sports/common/betslip/modules/reward-tokens/selectors.ts","packages/sports/web/app/src/acca-boost-base/acca-boost-utils.ts","packages/sports/common/betslip/modules/betslip-bar/services/betslip-bar-visibility.service.ts","packages/sports/common/betslip/modules/hidden-market/hidden-market.selectors.ts","packages/sports/common/betslip/base/store/actions.ts","packages/sports/common/betslip/modules/settings/actions.ts","packages/sports/common/betslip/modules/edit-bet/actions.ts","packages/sports/common/betslip/integration/sports-injection-services.ts","packages/sports/common/betslip/integration/betslip-integration.service.ts","packages/sports/web/app/src/bottom-nav/bottom-nav-actions.service.ts","packages/sports/web/app/src/banner/game-launcher.resolver.ts","packages/sports/web/app/src/common/local-storage.service.ts","packages/sports/web/app/src/competition-list/competition-route.service.ts","packages/sports/web/app/src/competition-list/competition-list-seo.service.ts","packages/sports/web/app/src/seo/seo-content.service.ts","packages/sports/web/app/src/option-pick/pick-source.provider.ts","packages/sports/web/app/src/widget/core/modular-config-accessor.service.ts","packages/sports/web/app/src/widget/core/modular-pick-source.service.ts","packages/sports/common/betslip/modules/quick-bet/quick-bet.state.ts","packages/sports/common/betslip/modules/combo-bet/selectors.ts","packages/sports/common/betslip/modules/validation/errors/notify-user-error.ts","packages/sports/common/betslip/modules/validation/errors/result/pick-invisible.ts","packages/sports/common/betslip/modules/validation/errors/pre-check/pick-locked-pre-check-error.ts","packages/sports/common/betslip/modules/betslip-bar/selectors.ts","packages/sports/common/betslip/modules/settings/selectors.ts","packages/sports/common/betslip/modules/summary/selectors.ts","packages/sports/common/betslip/modules/tracking/models.ts","packages/sports/common/betslip/modules/quick-bet/quick-bet.selectors.ts","packages/sports/web/app/src/common/hide-header.service.ts","packages/sports/libs/grid/core/feature/model/src/grid.model.ts","node_modules/resize-observer-polyfill/dist/ResizeObserver.es.js","packages/sports/libs/common/core/utils/dom/src/lib/resize-observer.service.ts","packages/sports/web/app/src/grid/grid-breakpoint.service.ts","packages/sports/libs/common/core/utils/hooks/src/lib/hooks-wireup.ts","packages/sports/libs/common/core/utils/hooks/src/lib/on-route-resolve.ts","packages/sports/libs/common/core/utils/rxjs/src/lib/buffer-with-size.ts","packages/sports/libs/common/core/utils/collection/src/lib/collection.ts","packages/sports/web/app/src/widget/core/widget-skeleton.model.ts","packages/sports/web/app/src/widget/core/widget-registry.service.ts","packages/sports/web/app/src/widget/core/widgets-to-widget-types.util.ts","packages/sports/web/app/src/widget/core/widget-right-column.service.ts","packages/sports/web/app/src/widget/core/modular-tracking.service.ts","packages/sports/web/app/src/widget/common/widget-context-refresh-processor.ts","packages/sports/web/app/src/widget/core/widget-refresh.service.ts","packages/sports/web/app/src/widget/core/widget-skeleton-renderer.service.ts","packages/sports/web/app/src/widget/core/widget-slot.component.ts","packages/sports/web/app/src/widget/core/widget-tracking.service.ts","packages/sports/web/app/src/widget/core/widget-layout-hook.ts","packages/sports/web/app/src/widget/core/widget-layout.component.html","packages/sports/web/app/src/widget/core/widget-layout.component.ts","packages/sports/libs/common/core/utils/element-provider/src/lib/element-provider.ts","packages/sports/libs/modal/feature/src/dialog/modal-dialog-container.directive.ts","packages/sports/web/app/src/widget/core/widget-column.component.html","packages/sports/web/app/src/widget/core/widget-column.component.ts","packages/sports/web/app/src/widget/core/widget-core.module.ts","packages/sports/web/app/src/betslip/betslip-digital-component-loader.service.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-delete.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-update.factory.ts","packages/sports/libs/betting-offer/feature/fixture-factories/src/mappers/detailed-fixture.factory.ts","packages/sports/web/app/src/statistics/statistics-config.service.ts","packages/sports/libs/betting-offer/feature/offer-service/src/betting-offer.service.ts","packages/sports/web/app/src/betbuilder/services/betbuilder-mapper.service.ts","packages/sports/web/app/src/event-details-common/event-details.service.ts","packages/sports/web/app/src/betslip-base/models/pick-models.ts","packages/design-system/ui/rx-host-listener/src/rx-host-listener.ts","node_modules/@angular/forms/fesm2022/forms.mjs","packages/design-system/ui/radio-button/src/radio-button.component.ts","packages/design-system/ui/radio-button/src/index.ts","packages/vanilla/lib/shared/browser/src/trust-as-html.pipe.ts","packages/vanilla/lib/shared/browser/src/format.pipe.ts","packages/vanilla/lib/shared/browser/src/html-attrs.directive.ts","packages/vanilla/lib/shared/browser/src/trust-as-resource-url.pipe.ts","packages/vanilla/lib/shared/browser/src/iframe.component.ts","packages/vanilla/lib/shared/image/image.html","packages/vanilla/lib/shared/image/image.component.ts","packages/vanilla/lib/shared/account-menu/account-menu-onboarding.service.ts","packages/vanilla/lib/shared/account-menu/account-menu.client-config.ts","packages/vanilla/lib/shared/account-menu/account-menu-data.service.ts","packages/vanilla/lib/shared/account-menu/account-menu.models.ts","packages/vanilla/lib/shared/account-menu/account-menu-tasks.service.ts","packages/vanilla/lib/shared/account-menu/account-menu-router.ts","packages/vanilla/lib/shared/account-menu/account-menu.html","packages/vanilla/lib/shared/account-menu/account-menu.component.ts","packages/vanilla/lib/shared/overlay-factory/src/overlay-factory.models.ts","packages/vanilla/lib/shared/overlay-factory/src/overlay.factory.ts","packages/vanilla/lib/shared/icons/src/icons.client-config.ts","packages/vanilla/lib/shared/icons/src/icon-fast.service.ts","packages/vanilla/lib/shared/icons/src/icon-fast.component.ts","packages/vanilla/lib/features/header-bar/src/header-bar.client-config.ts","packages/vanilla/lib/features/header-bar/src/header-bar.service.ts","packages/vanilla/lib/features/header-bar/src/header-bar-bootstrap.service.ts","packages/vanilla/lib/features/header-bar/src/header-bar.feature.ts","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.client-config.ts","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.html","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.component.ts","packages/vanilla/lib/shared/confirm-popup/src/confirm-popup.service.ts","packages/vanilla/lib/features/header-bar/src/header-bar.models.ts","packages/vanilla/lib/features/header-bar/src/header-bar.html","packages/vanilla/lib/features/header-bar/src/header-bar.component.ts","packages/vanilla/lib/features/header-bar/src/lh-header-bar.component.ts","packages/vanilla/lib/features/header-bar/src/header-bar.component.html","packages/vanilla/lib/features/language-switcher/src/language-switcher.client-config.ts","packages/vanilla/lib/features/language-switcher/src/language-item.html","packages/vanilla/lib/features/language-switcher/src/language-item.component.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-tracking.service.ts","packages/vanilla/lib/features/flags/src/flags.service.ts","packages/vanilla/lib/features/language-switcher/src/default-language-switcher-urls-provider.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-urls-provider.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher.service.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher.tokens.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-menu.html","packages/vanilla/lib/features/language-switcher/src/language-switcher-menu.component.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-overlay.service.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-bootstrap.service.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher.feature.ts","packages/vanilla/lib/features/language-switcher/src/language-switcher-radio-menu.html","packages/vanilla/lib/features/language-switcher/src/language-switcher-radio-menu.component.ts","packages/vanilla/lib/features/language-switcher/src/seo-language-links.html","packages/vanilla/lib/features/language-switcher/src/seo-language-links.component.ts","packages/vanilla/lib/features/language-switcher/src/responsive-language-switcher.html","packages/vanilla/lib/features/language-switcher/src/responsive-language-switcher.component.ts","packages/sports/web/app/src/world-cup-hub/world-cup-hub.service.ts","packages/sports/web/app/src/calendar/calendar-cache.service.ts","packages/sports/web/app/src/event-list-shared/sport/sport-sub-navigation.service.ts","packages/sports/web/app/src/layout/slot-layout.service.ts","packages/sports/libs/event-subscription/feature/base-subscription/src/base-subscription.service.ts","packages/sports/web/app/src/event-subscription/player-stats-subscription-service.ts","node_modules/@babel/runtime/helpers/esm/typeof.js","packages/sports/libs/loading-indicator-feature/src/custom-loading-indicator-handler.ts","packages/sports/libs/loading-indicator-feature/src/custom-loading-indicator-switch.service.ts","packages/sports/libs/loading-indicator-feature/src/custom-loading-indicator.service.ts","packages/sports/libs/loading-indicator-feature/src/loading-indicator.html","packages/sports/libs/loading-indicator-feature/src/loading-indicator.component.ts","packages/sports/libs/loading-indicator-feature/src/loading-indicator.module.ts","node_modules/fflate/esm/browser.js","node_modules/jspdf/dist/jspdf.es.min.js","packages/sports/web/app/src/print/print-error-dialog.service.ts","packages/sports/web/app/src/print/print-loading-indicator.component.ts","packages/sports/web/app/src/print/print-loading-indicator.html","packages/sports/web/app/src/print/print-loading-indicator-dialog.service.ts","packages/sports/web/app/src/print/print.models.ts","packages/sports/web/app/src/print/print.service.ts"],"sourcesContent":["!function (win) {\n /**\n * FastDom\n *\n * Eliminates layout thrashing\n * by batching DOM read/write\n * interactions.\n *\n * @author Wilson Page \n * @author Kornel Lesinski \n */\n\n 'use strict';\n\n /**\n * Mini logger\n *\n * @return {Function}\n */\n var debug = 0 ? console.log.bind(console, '[fastdom]') : function () {};\n\n /**\n * Normalized rAF\n *\n * @type {Function}\n */\n var raf = win.requestAnimationFrame || win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || win.msRequestAnimationFrame || function (cb) {\n return setTimeout(cb, 16);\n };\n\n /**\n * Initialize a `FastDom`.\n *\n * @constructor\n */\n function FastDom() {\n var self = this;\n self.reads = [];\n self.writes = [];\n self.raf = raf.bind(win); // test hook\n debug('initialized', self);\n }\n FastDom.prototype = {\n constructor: FastDom,\n /**\n * We run this inside a try catch\n * so that if any jobs error, we\n * are able to recover and continue\n * to flush the batch until it's empty.\n *\n * @param {Array} tasks\n */\n runTasks: function (tasks) {\n debug('run tasks');\n var task;\n while (task = tasks.shift()) task();\n },\n /**\n * Adds a job to the read batch and\n * schedules a new frame if need be.\n *\n * @param {Function} fn\n * @param {Object} ctx the context to be bound to `fn` (optional).\n * @public\n */\n measure: function (fn, ctx) {\n debug('measure');\n var task = !ctx ? fn : fn.bind(ctx);\n this.reads.push(task);\n scheduleFlush(this);\n return task;\n },\n /**\n * Adds a job to the\n * write batch and schedules\n * a new frame if need be.\n *\n * @param {Function} fn\n * @param {Object} ctx the context to be bound to `fn` (optional).\n * @public\n */\n mutate: function (fn, ctx) {\n debug('mutate');\n var task = !ctx ? fn : fn.bind(ctx);\n this.writes.push(task);\n scheduleFlush(this);\n return task;\n },\n /**\n * Clears a scheduled 'read' or 'write' task.\n *\n * @param {Object} task\n * @return {Boolean} success\n * @public\n */\n clear: function (task) {\n debug('clear', task);\n return remove(this.reads, task) || remove(this.writes, task);\n },\n /**\n * Extend this FastDom with some\n * custom functionality.\n *\n * Because fastdom must *always* be a\n * singleton, we're actually extending\n * the fastdom instance. This means tasks\n * scheduled by an extension still enter\n * fastdom's global task queue.\n *\n * The 'super' instance can be accessed\n * from `this.fastdom`.\n *\n * @example\n *\n * var myFastdom = fastdom.extend({\n * initialize: function() {\n * // runs on creation\n * },\n *\n * // override a method\n * measure: function(fn) {\n * // do extra stuff ...\n *\n * // then call the original\n * return this.fastdom.measure(fn);\n * },\n *\n * ...\n * });\n *\n * @param {Object} props properties to mixin\n * @return {FastDom}\n */\n extend: function (props) {\n debug('extend', props);\n if (typeof props != 'object') throw new Error('expected object');\n var child = Object.create(this);\n mixin(child, props);\n child.fastdom = this;\n\n // run optional creation hook\n if (child.initialize) child.initialize();\n return child;\n },\n // override this with a function\n // to prevent Errors in console\n // when tasks throw\n catch: null\n };\n\n /**\n * Schedules a new read/write\n * batch if one isn't pending.\n *\n * @private\n */\n function scheduleFlush(fastdom) {\n if (!fastdom.scheduled) {\n fastdom.scheduled = true;\n fastdom.raf(flush.bind(null, fastdom));\n debug('flush scheduled');\n }\n }\n\n /**\n * Runs queued `read` and `write` tasks.\n *\n * Errors are caught and thrown by default.\n * If a `.catch` function has been defined\n * it is called instead.\n *\n * @private\n */\n function flush(fastdom) {\n debug('flush');\n var writes = fastdom.writes;\n var reads = fastdom.reads;\n var error;\n try {\n debug('flushing reads', reads.length);\n fastdom.runTasks(reads);\n debug('flushing writes', writes.length);\n fastdom.runTasks(writes);\n } catch (e) {\n error = e;\n }\n fastdom.scheduled = false;\n\n // If the batch errored we may still have tasks queued\n if (reads.length || writes.length) scheduleFlush(fastdom);\n if (error) {\n debug('task errored', error.message);\n if (fastdom.catch) fastdom.catch(error);else throw error;\n }\n }\n\n /**\n * Remove an item from an Array.\n *\n * @param {Array} array\n * @param {*} item\n * @return {Boolean}\n */\n function remove(array, item) {\n var index = array.indexOf(item);\n return !!~index && !!array.splice(index, 1);\n }\n\n /**\n * Mixin own properties of source\n * object into the target.\n *\n * @param {Object} target\n * @param {Object} source\n */\n function mixin(target, source) {\n for (var key in source) {\n if (source.hasOwnProperty(key)) target[key] = source[key];\n }\n }\n\n // There should never be more than\n // one instance of `FastDom` in an app\n var exports = win.fastdom = win.fastdom || new FastDom(); // jshint ignore:line\n\n // Expose to CJS & AMD\n if (typeof define == 'function') define(function () {\n return exports;\n });else if (typeof module == 'object') module.exports = exports;\n}(typeof window !== 'undefined' ? window : typeof this != 'undefined' ? this : globalThis);","import { InjectionToken } from '@angular/core';\n\nexport interface ModalWindowConfig {\n animatePopupDismiss?: boolean;\n showCloseIcon?: boolean;\n}\n\nexport const MODAL_WINDOW_CONFIG = new InjectionToken('MODAL_WINDOW_CONFIG');\n","import { DOCUMENT } from '@angular/common';\nimport { Injectable, Renderer2, RendererFactory2, inject } from '@angular/core';\n\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport fastdom from 'fastdom';\nimport { Observable, first, from, of } from 'rxjs';\n\nimport { OpenedModalsStorageService } from './opened-modals-storage.service';\n\ninterface PanOffset {\n currentY?: number;\n currentX?: number;\n}\n\ninterface BackgroundAnimationOptions {\n duration: number;\n fromScale?: string;\n toScale?: string;\n fromPosition?: number;\n toPosition?: number;\n isEdpBackground?: boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ModalAnimationHelperService {\n readonly closingRatioY = 0.2;\n readonly closingRatioX = 0.5;\n readonly minBackgroundScaleValue = 0.9;\n readonly minBackgroundScale = `scale(${this.minBackgroundScaleValue})`;\n readonly maxBackgroundScale = 'scale(1)';\n readonly minCoverElementPosition = 'translateY(-100%)';\n readonly maxCoverElementPosition = 'translateY(0%)';\n readonly animationDurationY = 500;\n readonly animationDurationX = 350;\n readonly maxBackdropOpacity = 0.25;\n readonly maxBackgroundPanValue = Math.round(window.screen.width * 0.33);\n private renderer: Renderer2;\n private _backgroundWindow: HTMLElement | null;\n private readonly _doc = inject(DOCUMENT);\n\n private get slotApp(): HTMLElement | null {\n return this._doc.querySelector('.slot-app');\n }\n\n private get backgroundWindow(): HTMLElement | null {\n return (this._backgroundWindow ??= this._doc.querySelector('.app-root'));\n }\n\n private get modalWindow(): HTMLElement | null {\n const modals = this._doc.querySelectorAll('ms-modal-window');\n return modals[modals.length - 1];\n }\n\n constructor(\n private openedModals: OpenedModalsStorageService,\n private rendererFactory: RendererFactory2,\n ) {\n this.renderer = this.rendererFactory.createRenderer(null, null);\n }\n\n addSlideWithScale(): void {\n this.tryHideSlotApp();\n\n this.animateModal(true, { currentY: 0 }, this.animationDurationY).subscribe(() => {\n this.tryShowSlotApp();\n });\n\n fastdom.mutate(() => {\n requestAnimationFrame(() => {\n this.animateCoverElement(this.maxCoverElementPosition, this.minCoverElementPosition, this.animationDurationY);\n this.animateBackdrop(this.maxBackdropOpacity, 0, this.animationDurationY);\n this.animateBackground({\n fromScale: this.minBackgroundScale,\n toScale: this.maxBackgroundScale,\n duration: this.animationDurationY,\n isEdpBackground: this.openedModals.edpModal && this.openedModals.count > 1,\n }).subscribe(() => {\n if (this.openedModals.count < 1) {\n this.renderer.removeStyle(this.backgroundWindow, 'will-change');\n this.renderer.removeAttribute(this.backgroundWindow, 'style');\n }\n });\n });\n });\n }\n\n addSlideOutHorizontal(): void {\n this.animateBackdrop(this.maxBackdropOpacity, 0, this.animationDurationX);\n this.animateBackground({ fromPosition: this.maxBackgroundPanValue, toPosition: 0, duration: this.animationDurationX }).subscribe();\n this.animateModal(true, { currentX: 0 }, this.animationDurationX).subscribe();\n }\n\n animateModal(shouldSlideOut: boolean, { currentY, currentX }: PanOffset, duration: number): Observable {\n if (!this.modalWindow || duration === 0) {\n return of(undefined);\n }\n\n let keyframes: Keyframe[] = [];\n if (isDefined(currentY)) {\n keyframes = [{ top: `${currentY}px` }, { top: shouldSlideOut ? `${window.screen.height}px` : '0px' }];\n } else if (isDefined(currentX)) {\n this.renderer.setStyle(this.modalWindow, 'min-width', '100vw');\n keyframes = [{ left: `${currentX}px` }, { left: shouldSlideOut ? `${window.screen.width}px` : '0px' }];\n }\n\n return from(this.modalWindow.animate(keyframes, this.animationOptions(duration)).finished).pipe(first());\n }\n\n calculateAnimationTime({ currentY, currentX }: PanOffset): number {\n if (currentY) {\n const timeForDistance = Math.round(this.animationDurationY * (currentY / window.screen.height));\n return currentY < window.screen.height * this.closingRatioY ? timeForDistance : this.animationDurationY - timeForDistance;\n } else if (currentX) {\n const timeForDistance = Math.round(this.animationDurationX * (currentX / window.screen.width));\n return currentX < window.screen.width * this.closingRatioX ? timeForDistance : this.animationDurationX - timeForDistance;\n }\n\n return 0;\n }\n\n animateBackdrop(fromOpacity: number, toOpacity: number, duration: number): void {\n const backdrops = this._doc.querySelectorAll('ms-modal-backdrop');\n const modalBackdrop = backdrops[backdrops.length - 1];\n\n if (this.openedModals.count > 1 || !modalBackdrop || duration === 0) {\n return;\n }\n\n modalBackdrop.animate([{ opacity: fromOpacity }, { opacity: toOpacity }], this.animationOptions(duration));\n }\n\n animateCoverElement(fromPosition: string, toPosition: string, duration: number): void {\n const coverElements = this._doc.querySelectorAll('ms-modal-cover');\n const coverElement = coverElements[coverElements.length - 1];\n\n if (this.openedModals.count > 1 || !coverElement || duration === 0) {\n return;\n }\n\n coverElement.animate([{ transform: fromPosition }, { transform: toPosition }], this.animationOptions(duration)).finished.then(() => {\n if (toPosition !== this.minCoverElementPosition) {\n this.renderer.setStyle(coverElement, 'transform', this.minCoverElementPosition);\n }\n });\n }\n\n animateBackground({\n fromScale,\n toScale,\n duration,\n fromPosition,\n toPosition,\n isEdpBackground,\n }: BackgroundAnimationOptions): Observable {\n if (!this.backgroundWindow || duration === 0) {\n return of(undefined);\n }\n\n let backgroundToAnimate = isEdpBackground ? this._doc.querySelector('.event-details-modal') : this.backgroundWindow;\n if (backgroundToAnimate) {\n let keyframes: Keyframe[] = [];\n if (isDefined(fromScale) && isDefined(toScale)) {\n this.renderer.setStyle(backgroundToAnimate, 'will-change', 'transform');\n this.setTransformOriginCoordinates();\n keyframes = [{ transform: fromScale }, { transform: toScale }];\n } else if (isDefined(fromPosition) && isDefined(toPosition)) {\n this.renderer.setStyle(backgroundToAnimate, 'display', 'block');\n this.renderer.setStyle(backgroundToAnimate, 'position', 'relative');\n this.renderer.setStyle(backgroundToAnimate, 'will-change', 'right');\n keyframes = [{ right: `${fromPosition}px` }, { right: `${toPosition}px` }];\n }\n\n return from(backgroundToAnimate.animate(keyframes, this.animationOptions(duration)).finished).pipe(first());\n }\n return of(undefined);\n }\n\n setTransformOriginCoordinates(): void {\n if (!this.backgroundWindow) {\n return;\n }\n\n const scrollY = this.getScrollValueFromBody();\n const viewPortY = window.screen.height / 2;\n const viewPortX = Math.round(window.screen.width / 2);\n this.renderer.setStyle(this.backgroundWindow, 'transform-origin', `${viewPortX}px ${Math.round(scrollY + viewPortY)}px`);\n }\n\n tryShowSlotApp(): void {\n if (this.slotApp && this.openedModals.count <= 1) {\n this.renderer.removeStyle(this.slotApp, 'visibility');\n }\n }\n\n tryHideSlotApp(): void {\n if (this.slotApp && this.openedModals.count <= 1) {\n this.renderer.setStyle(this.slotApp, 'visibility', 'hidden');\n }\n }\n\n private getScrollValueFromBody(): number {\n const scrollValue = Number.parseInt(this._doc.body.style.top.replace(/^\\D+/g, ''), 10);\n if (!isNaN(scrollValue)) {\n return scrollValue;\n }\n return 0;\n }\n\n private animationOptions(duration: number): KeyframeAnimationOptions {\n return {\n duration,\n iterations: 1,\n easing: 'ease',\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { HtmlNode } from '@frontend/vanilla/core';\nimport { remove } from 'lodash-es';\n\nimport { ModalRef } from '../common/modal-ref';\nimport { PopupAction } from '../common/modal-window.component';\nimport { ModalDialogComponent } from './modal-dialog.component';\n\n@Injectable({ providedIn: 'root' })\nexport class OpenedModalsStorageService {\n private openedModals: ModalRef[] = [];\n\n constructor(private htmlNode: HtmlNode) {}\n\n set openedModal(modalRef: ModalRef) {\n this.openedModals.push(modalRef);\n }\n\n get activeModal(): ModalRef | undefined {\n return this.openedModals[this.openedModals.length - 1];\n }\n\n get count(): number {\n return this.openedModals.length;\n }\n\n removeCurrent(modalRef: ModalRef): void {\n remove(this.openedModals, (m) => m === modalRef);\n }\n\n tryCloseSeeAll(isBabPopup: boolean): void {\n if (!isBabPopup) {\n return;\n }\n\n this.openedModals\n .find((m) => (m.componentInstance as ModalDialogComponent)?.dialogSettings.headerSelector === 'ms-option-panel-header')\n ?.close({ closeAction: PopupAction.ManualClose });\n\n this.htmlNode.setCssClass('sheet-view-for-market-for-sgp', false);\n }\n\n get edpModal(): ModalRef | undefined {\n return this.openedModals.find((m) => (m.componentInstance as ModalDialogComponent)?.dialogSettings.headerSelector === 'event-details-modal');\n }\n}\n","import { DOCUMENT } from '@angular/common';\nimport { DestroyRef, Inject, Injectable, InjectionToken, Renderer2, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { HammerGestureConfig } from '@angular/platform-browser';\n\nimport { AppConfig } from '@frontend/sports/common/client-config-data-access';\nimport { MutationObserverService } from '@frontend/sports/common/core/utils/dom';\nimport { fromSportsLeave } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport fastdom from 'fastdom';\nimport { TouchMouseInput } from 'hammerjs';\nimport { Subscription, animationFrames, delay, exhaustMap, filter, fromEvent, merge, take, tap } from 'rxjs';\n\nimport { GestureOptions, PopupAction } from '../common/modal-window.component';\nimport { ModalAnimationHelperService } from './modal-animation-helper.service';\nimport { OpenedModalsStorageService } from './opened-modals-storage.service';\n\nexport const VERTICAL_GESTURE_CONFIG = new InjectionToken('ModalGestureVerticalConfig');\n\n@Injectable()\nexport class ModalGestureVerticalConfig extends HammerGestureConfig {\n override overrides = {\n pan: {\n direction: Hammer.DIRECTION_VERTICAL,\n },\n };\n\n override options = {\n inputClass: TouchMouseInput,\n touchAction: 'pan-y',\n };\n}\n\n@Injectable()\nexport class ModalGestureService {\n private readonly willChange = 'will-change';\n private isDragging = false;\n private isAnimating = false;\n private currentY = 0;\n private readonly destroyRef = inject(DestroyRef);\n private currentBackgroundScale = this.animationHelper.minBackgroundScaleValue;\n private currentBackdropOpacity = this.animationHelper.maxBackdropOpacity;\n private modalWindow: HTMLElement;\n private backgroundWindow: HTMLElement | null;\n private readonly closingVelocity = 0.5; //velocity expressed in px/ms.\n private readonly coverElementHeight = window.screen.height * 0.05;\n private isEdpModalBackground: boolean;\n private readonly sportsLeaveSub: Subscription;\n private readonly _doc = inject(DOCUMENT);\n private _scrollContainer: HTMLElement | null;\n private _headerHeight?: number;\n private scrollTargetQuerySelector?: string;\n private headerSelector?: string;\n\n private get modalBackdrop(): HTMLElement {\n const backdrops = this._doc.querySelectorAll('ms-modal-backdrop');\n return backdrops[backdrops.length - 1];\n }\n\n private get modalCover(): HTMLElement {\n const covers = this._doc.querySelectorAll('ms-modal-cover');\n return covers[covers.length - 1];\n }\n\n private get edpCoverElement(): HTMLElement | null {\n return this._doc.querySelectorAll('.modal-gestures-cover')[0];\n }\n\n private get slotApp(): HTMLElement | null {\n return this._doc.querySelector('.slot-app');\n }\n\n private get scrollContainer(): HTMLElement | null {\n if (!this.scrollTargetQuerySelector) {\n return null;\n }\n\n return (this._scrollContainer ??= this.modalWindow.querySelector(this.scrollTargetQuerySelector));\n }\n\n private get headerHeight(): number {\n if (!this.headerSelector) {\n return 0;\n }\n\n return (this._headerHeight ??= this.modalWindow.querySelector(this.headerSelector)?.clientHeight ?? 0);\n }\n\n constructor(\n @Inject(VERTICAL_GESTURE_CONFIG) private hammerGestureConfig: HammerGestureConfig,\n private openedModals: OpenedModalsStorageService,\n private appConfig: AppConfig,\n private renderer: Renderer2,\n private animationHelper: ModalAnimationHelperService,\n private mutationObserverService: MutationObserverService,\n ) {\n if (this.appConfig.modalDialogGesturesEnabled) {\n this.destroyRef.onDestroy(() => {\n this.sportsLeaveSub.unsubscribe();\n this.onDestroyCleanup();\n });\n\n this.sportsLeaveSub = fromSportsLeave().subscribe(() => {\n this.onDestroyCleanup();\n });\n }\n }\n\n private onDestroyCleanup(): void {\n if (!this.openedModals.count && this.modalWindow) {\n this.renderer.removeAttribute(this.modalWindow, 'style');\n this.renderer.removeAttribute(this.backgroundWindow, 'style');\n }\n }\n\n addGestures(modalWindow: unknown, { enabled, headerSelector, scrollTargetQuerySelector }: GestureOptions): void {\n if (!enabled || !(modalWindow instanceof HTMLElement)) {\n return;\n }\n\n this.isEdpModalBackground = !!this.openedModals.edpModal;\n this.backgroundWindow = this.isEdpModalBackground\n ? this._doc.querySelector('.event-details-modal')\n : this._doc.querySelector('.app-root');\n\n this.modalWindow = modalWindow;\n this.scrollTargetQuerySelector = scrollTargetQuerySelector;\n this.headerSelector = headerSelector;\n\n if (this.headerSelector === 'bs-digital-balance-header' && this.openedModals.count >= 1) {\n this.mutationObserverService\n .observe(this.modalWindow)\n .pipe(\n filter((ms) => ms.some((m) => Array.from(m.addedNodes).some((node) => node.nodeName.toLowerCase() === 'bs-betslip'))),\n delay(this.animationHelper.animationDurationY),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe(() => {\n this.initCoverElement(true);\n });\n } else {\n this.initCoverElement();\n this.initModalBackdrop();\n }\n\n this.handlePanEvents(this.modalWindow);\n this.modifyModalHeight();\n }\n\n private initModalBackdrop(): void {\n if (this.openedModals.count <= 1 || this.isEdpModalBackground) {\n fastdom.mutate(() => {\n this.renderer.setStyle(this.backgroundWindow, this.willChange, 'transform');\n requestAnimationFrame(() => {\n this.animationHelper.animateBackdrop(0, this.animationHelper.maxBackdropOpacity, this.animationHelper.animationDurationY);\n // We use the coverElement to hide content above the header.\n this.animationHelper.animateCoverElement(\n this.animationHelper.minCoverElementPosition,\n this.animationHelper.maxCoverElementPosition,\n this.animationHelper.animationDurationY,\n );\n this.renderer.setStyle(this.backgroundWindow, 'display', 'block');\n this.animateBackground(this.animationHelper.minBackgroundScale, this.animationHelper.animationDurationY);\n });\n });\n }\n }\n\n private initCoverElement(betslipCase?: boolean): void {\n if (this.openedModals.count > 1 && !this.openedModals.edpModal && !betslipCase) {\n return;\n }\n\n if (this.openedModals.edpModal) {\n this.renderer.addClass(this.modalCover, 'event-details-overlay');\n } else {\n this.renderer.setStyle(this.modalCover, 'height', `${this.coverElementHeight}px`);\n }\n }\n\n private handlePanEvents(panElement: HTMLElement): void {\n const hammer = this.hammerGestureConfig.buildHammer(panElement);\n\n fromEvent(hammer, 'panstart')\n .pipe(\n filter((pan) => this.allowedToPan(pan)),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe(() =>\n fastdom.mutate(() => {\n this.handlePanstart();\n }),\n );\n\n fromEvent(hammer, 'panmove')\n .pipe(\n filter(\n (pan) => (pan.direction === Hammer.DIRECTION_UP || pan.direction === Hammer.DIRECTION_DOWN) && this.isDragging && pan.deltaY > 0,\n ),\n takeUntilDestroyed(this.destroyRef),\n exhaustMap((pan) =>\n animationFrames().pipe(\n take(1),\n filter(() => !this.isAnimating),\n tap(() =>\n fastdom.mutate(() => {\n this.handlePanmove(pan);\n }),\n ),\n ),\n ),\n )\n .subscribe();\n\n merge(\n fromEvent(hammer, 'panend'),\n fromEvent(panElement, 'touchend', { capture: true }).pipe(delay(50)), // Wait if panend emits\n )\n .pipe(\n filter(() => this.isDragging),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((event) =>\n fastdom.mutate(() => {\n this.handlePanend((event as HammerInput)?.velocity || 0);\n }),\n );\n }\n\n private allowedToPan(pan: HammerInput): boolean {\n const scrolledTop = !!(this.scrollContainer && this.scrollContainer.scrollTop < 10);\n const panOnHeader = this.headerHeight ? Math.round((pan.srcEvent as TouchEvent).touches[0].clientY) <= this.headerHeight : true;\n\n return !this.isDragging && !this.isAnimating && pan.direction === Hammer.DIRECTION_DOWN && (scrolledTop || panOnHeader);\n }\n\n private handlePanstart(): void {\n this.isDragging = true;\n if (this.openedModals.count <= 1) {\n this.renderer.setStyle(this.backgroundWindow, 'display', 'block');\n this.renderer.setStyle(this.backgroundWindow, this.willChange, 'transform');\n this.animationHelper.setTransformOriginCoordinates();\n }\n this.animationHelper.tryHideSlotApp();\n this.renderer.setStyle(this.modalWindow, 'height', '100%');\n this.renderer.setStyle(this.modalWindow, this.willChange, 'top');\n }\n\n private handlePanmove(event: HammerInput): void {\n this.currentY = event.deltaY;\n if (this.currentY <= window.screen.height) {\n this.setBackgroundScaleOnPanMove(this.currentY);\n this.setModalTop(this.currentY);\n } else {\n this.panClose();\n }\n }\n\n private handlePanend(velocity: number): void {\n if (!this.isDragging) {\n return;\n }\n\n this.isDragging = false;\n this.isAnimating = true;\n\n let animationTime = this.animationHelper.calculateAnimationTime({ currentY: this.currentY });\n const shouldClose = this.currentY > window.innerHeight * this.animationHelper.closingRatioY;\n\n if (velocity >= this.closingVelocity) {\n this.closeWithVelocity(shouldClose, velocity, animationTime);\n } else {\n if (shouldClose) {\n this.animateClose(animationTime);\n } else {\n this.animateModal(false, this.currentY, animationTime);\n this.animationHelper.animateBackdrop(\n Number.parseFloat(this.modalBackdrop.style.opacity),\n this.animationHelper.maxBackdropOpacity,\n animationTime,\n );\n this.animationHelper.animateCoverElement(\n this.modalCover.style.transform,\n this.animationHelper.maxCoverElementPosition,\n animationTime,\n );\n this.animateBackground(this.animationHelper.minBackgroundScale, animationTime, this.backgroundWindow?.style.transform);\n }\n }\n }\n\n private closeWithVelocity(shouldClose: boolean, velocity: number, animationTime: number): void {\n const velocityAnimationTime = (window.innerHeight - this.currentY) / velocity;\n\n if (!shouldClose || (velocityAnimationTime > 0 && velocityAnimationTime < animationTime)) {\n this.animateClose(velocityAnimationTime);\n } else {\n this.animateClose(animationTime);\n }\n }\n\n private animateClose(animationTime: number): void {\n this.setModalTop(window.innerHeight);\n this.animateModal(true, this.currentY, animationTime);\n this.animationHelper.animateBackdrop(Number.parseFloat(this.modalBackdrop.style.opacity), 0, animationTime);\n this.animationHelper.animateCoverElement(this.modalCover.style.transform, this.animationHelper.minCoverElementPosition, animationTime);\n this.animateBackground(this.animationHelper.maxBackgroundScale, animationTime, this.backgroundWindow?.style.transform);\n }\n\n private setModalTop(top = 0): void {\n if (top < 0) {\n return;\n }\n if (!this.isAnimating) {\n this.renderer.setStyle(this.modalWindow, 'top', `${top}px`);\n }\n }\n\n private setBackgroundScaleOnPanMove(currentY: number): void {\n if (this.openedModals.count > 1 && !this.isEdpModalBackground) {\n return;\n }\n const currentPanValue = currentY / window.screen.height;\n this.renderer.setStyle(this.modalCover, 'transform', `translateY(-${currentPanValue * 100}%)`);\n this.currentBackdropOpacity =\n this.animationHelper.maxBackdropOpacity - Number((this.animationHelper.maxBackdropOpacity * (currentY / window.screen.width)).toFixed(2));\n this.renderer.setStyle(this.modalBackdrop, 'opacity', `${this.currentBackdropOpacity}`);\n this.currentBackgroundScale = this.animationHelper.minBackgroundScaleValue + currentPanValue * 0.1;\n this.renderer.setStyle(this.backgroundWindow, 'transform', `scale(${this.currentBackgroundScale})`);\n }\n\n private animateBackground(toScale: string, duration: number, fromScale = this.animationHelper.maxBackgroundScale): void {\n this.animationHelper.animateBackground({ fromScale, toScale, duration, isEdpBackground: this.isEdpModalBackground }).subscribe(() => {\n this.renderer.removeStyle(this.backgroundWindow, this.willChange);\n this.renderer.removeStyle(this.backgroundWindow, 'transform');\n this.renderer.removeStyle(this.modalWindow, 'height');\n this.renderer.removeStyle(this.modalWindow, this.willChange);\n this.renderer.removeAttribute(this.modalWindow, 'style');\n\n if (toScale === this.animationHelper.maxBackgroundScale) {\n if (this.openedModals.count === 1 && this.openedModals.edpModal) {\n this.renderer.removeClass(this.edpCoverElement, 'modal-gestures-edp-cover');\n this.renderer.setStyle(this.edpCoverElement, 'height', `${this.coverElementHeight}px`);\n this.renderer.addClass(this.edpCoverElement, 'modal-gestures-cover');\n }\n this.panClose();\n }\n });\n }\n\n private animateModal(shouldSlideOut: boolean, currentY: number, duration: number): void {\n this.animationHelper.animateModal(shouldSlideOut, { currentY }, duration).subscribe(() => {\n this.renderer.removeAttribute(this.modalWindow, 'style');\n this.animationHelper.tryShowSlotApp();\n this.isAnimating = false;\n });\n }\n\n private panClose(): void {\n this.renderer.removeAttribute(this.backgroundWindow, 'style');\n this.openedModals.activeModal?.close({ closeAction: PopupAction.CloseByPan });\n }\n\n private modifyModalHeight(): void {\n this.modalWindow.addEventListener('animationstart', () => this.renderer.setStyle(this.modalWindow, 'height', '100%'));\n\n if (this.slotApp) {\n this.mutationObserverService\n .observe(this.slotApp, { attributes: true, attributeFilter: ['style'] })\n .pipe(\n filter((mutations) => mutations.some((mutation) => !(mutation.target as HTMLElement).getAttribute('style')?.includes('hidden'))),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe(() => {\n this.renderer.removeStyle(this.modalWindow, 'height');\n });\n }\n }\n}\n","import { DOCUMENT, Location } from '@angular/common';\nimport { DestroyRef, Inject, Injectable, InjectionToken, Renderer2, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { HammerGestureConfig } from '@angular/platform-browser';\n\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport fastdom from 'fastdom';\nimport { TouchMouseInput } from 'hammerjs';\nimport { animationFrames, exhaustMap, filter, fromEvent, take, tap } from 'rxjs';\n\nimport { GestureOptions, PopupAction } from '../common/modal-window.component';\nimport { ModalAnimationHelperService } from './modal-animation-helper.service';\nimport { OpenedModalsStorageService } from './opened-modals-storage.service';\n\nexport const HORIZONTAL_GESTURE_CONFIG = new InjectionToken('ModalGestureHorizontalConfig');\n\n@Injectable()\nexport class ModalGestureHorizontalConfig extends HammerGestureConfig {\n override overrides = {\n pan: {\n direction: Hammer.DIRECTION_HORIZONTAL,\n },\n };\n\n override options = {\n inputClass: TouchMouseInput,\n touchAction: 'pan-x',\n };\n}\n\n@Injectable()\nexport class ModalHorizontalGestureService {\n private modalWindow: HTMLElement;\n private isDragging = false;\n private isAnimating = false;\n private currentX = 0;\n private readonly closingVelocity = 0.5; //velocity expressed in px/ms.\n private currentBackdropOpacity = this.animationHelper.maxBackdropOpacity;\n private currentBackgroundPanValue = this.animationHelper.maxBackgroundPanValue;\n private backgroundWindow: HTMLElement | null;\n private modalBody: HTMLElement | null;\n private readonly destroyRef = inject(DestroyRef);\n private previousUrl: string | undefined;\n\n private get modalBackdrop(): HTMLElement {\n const backdrops = this.document.querySelectorAll('ms-modal-backdrop');\n return backdrops[backdrops.length - 1];\n }\n\n constructor(\n @Inject(HORIZONTAL_GESTURE_CONFIG) private hammerGestureConfig: HammerGestureConfig,\n @Inject(DOCUMENT) private readonly document: Document,\n private openedModals: OpenedModalsStorageService,\n private renderer: Renderer2,\n private animationHelper: ModalAnimationHelperService,\n private location: Location,\n private routerEventsService: RouterEventsService,\n ) {}\n\n addGestures(modalWindow: unknown, { enabled }: GestureOptions): void {\n if (!enabled || !(modalWindow instanceof HTMLElement)) {\n return;\n }\n\n this.previousUrl = this.trySetPreviousUrl(this.routerEventsService.urlHistory);\n this.backgroundWindow = this.document.querySelector('.app-root');\n this.modalWindow = modalWindow;\n this.modalBody = this.document.querySelector('.modal-body');\n const duration = 400;\n this.animationHelper.animateBackdrop(0, this.animationHelper.maxBackdropOpacity, duration);\n\n this.animateBackground(0, this.animationHelper.maxBackgroundPanValue, duration);\n this.modalWindow\n .animate([{ transform: 'translateX(100%)' }, { transform: 'translateX(0%)' }], {\n duration,\n iterations: 1,\n easing: 'cubic-bezier(0, 0.2, 0.2, 1)',\n })\n .finished.then(() => {\n this.handlePanEvents(this.modalWindow);\n });\n }\n\n private animateBackground(fromPosition: number, toPosition: number, duration: number): void {\n this.animationHelper.animateBackground({ fromPosition, toPosition, duration }).subscribe(() => {\n this.renderer.removeStyle(this.backgroundWindow, 'will-change');\n this.renderer.removeStyle(this.backgroundWindow, 'position');\n this.renderer.removeStyle(this.backgroundWindow, 'display');\n this.renderer.removeStyle(this.backgroundWindow, 'right');\n this.renderer.removeStyle(this.modalBody, 'overflow-y');\n });\n }\n\n private handlePanEvents(panElement: HTMLElement): void {\n const hammer = this.hammerGestureConfig.buildHammer(panElement);\n fromEvent(hammer, 'panstart')\n .pipe(\n filter(\n (pan) =>\n !this.isDragging &&\n !this.isAnimating &&\n pan.direction === Hammer.DIRECTION_RIGHT &&\n Math.round((pan.srcEvent as TouchEvent).touches[0].clientX) <= 50,\n ),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe(() =>\n fastdom.mutate(() => {\n this.handlePanstart();\n }),\n );\n\n fromEvent(hammer, 'panmove')\n .pipe(\n filter((pan) => this.isDragging && pan.deltaX > 0),\n takeUntilDestroyed(this.destroyRef),\n exhaustMap((pan) =>\n animationFrames().pipe(\n take(1),\n tap(() =>\n fastdom.mutate(() => {\n this.handlePanmove(pan);\n }),\n ),\n ),\n ),\n )\n .subscribe();\n\n fromEvent(hammer, 'panend')\n .pipe(\n filter(() => this.isDragging),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((pan) =>\n fastdom.mutate(() => {\n this.handlePanend(pan.velocity);\n }),\n );\n }\n\n private handlePanstart(): void {\n this.isDragging = true;\n this.renderer.setStyle(this.modalWindow, 'will-change', 'left');\n this.renderer.setStyle(this.modalWindow, 'min-width', '100vw');\n this.renderer.setStyle(this.modalBody, 'overflow-y', 'hidden');\n }\n\n private handlePanend(velocity: number): void {\n if (!this.isDragging) {\n return;\n }\n\n this.isDragging = false;\n this.isAnimating = true;\n const animationTime = this.animationHelper.calculateAnimationTime({ currentX: this.currentX });\n const shouldClose = this.currentX > window.screen.width * this.animationHelper.closingRatioX;\n\n if (velocity >= this.closingVelocity) {\n this.closeWithVelocity(shouldClose, velocity, animationTime);\n } else {\n if (shouldClose) {\n this.animateClose(animationTime);\n this.animateBackground(this.currentBackgroundPanValue, 0, this.animationHelper.animationDurationX);\n } else {\n this.animateModal(false, this.currentX, animationTime);\n this.animateBackground(\n this.currentBackgroundPanValue,\n this.animationHelper.maxBackgroundPanValue,\n this.animationHelper.animationDurationX,\n );\n }\n }\n }\n\n private closeWithVelocity(shouldClose: boolean, velocity: number, animationTime: number): void {\n const velocityAnimationTime = (window.innerWidth - this.currentX) / velocity;\n\n if (!shouldClose || (velocityAnimationTime > 0 && velocityAnimationTime < animationTime)) {\n this.animateClose(velocityAnimationTime);\n this.animateBackground(this.currentBackgroundPanValue, 0, velocityAnimationTime);\n } else {\n this.animateClose(animationTime);\n this.animateBackground(this.currentBackgroundPanValue, 0, animationTime);\n }\n }\n\n private animateClose(animationTime: number): void {\n this.setModalLeft(window.innerWidth);\n this.animateModal(true, this.currentX, animationTime, true);\n }\n\n private animateModal(shouldSlideOut: boolean, currentX: number, duration: number, shouldClose?: boolean): void {\n this.animationHelper.animateModal(shouldSlideOut, { currentX }, duration).subscribe(() => {\n this.renderer.removeAttribute(this.modalWindow, 'style');\n this.isAnimating = false;\n\n if (shouldClose) {\n this.panClose();\n }\n });\n }\n\n private handlePanmove(event: HammerInput): void {\n this.currentX = event.deltaX;\n\n if (this.currentX <= window.screen.width) {\n this.setModalLeft(this.currentX);\n } else {\n this.panClose();\n }\n }\n\n private setModalLeft(left = 0): void {\n if (left < 0) {\n return;\n }\n\n if (!this.isAnimating) {\n this.setBackgroundOffset(left);\n this.renderer.setStyle(this.modalWindow, 'left', `${left}px`);\n }\n }\n\n private panClose(): void {\n this.openedModals.activeModal?.close({ closeAction: PopupAction.CloseByPan });\n if (this.previousUrl) {\n this.location.replaceState(this.previousUrl);\n }\n }\n\n private trySetPreviousUrl(urlHistory?: string[]): string | undefined {\n const nonEdpUrls = urlHistory?.filter((url) => !url.includes('/events'));\n if (nonEdpUrls && nonEdpUrls.length !== 0) {\n return nonEdpUrls[nonEdpUrls.length - 1];\n }\n return undefined;\n }\n\n private setBackgroundOffset(currentX: number): void {\n if (this.openedModals.count > 1) {\n return;\n }\n\n this.currentBackgroundPanValue =\n this.animationHelper.maxBackgroundPanValue - Math.round(this.animationHelper.maxBackgroundPanValue * (currentX / window.screen.width));\n this.renderer.setStyle(this.backgroundWindow, 'display', 'block');\n this.renderer.setStyle(this.backgroundWindow, 'position', 'relative');\n this.renderer.setStyle(this.backgroundWindow, 'right', `${this.currentBackgroundPanValue}px`);\n\n this.currentBackdropOpacity =\n this.animationHelper.maxBackdropOpacity - Number((this.animationHelper.maxBackdropOpacity * (currentX / window.screen.width)).toFixed(2));\n this.renderer.setStyle(this.modalBackdrop, 'opacity', `${this.currentBackdropOpacity}`);\n }\n}\n","import { createAction } from '@ngrx/store';\n\nexport const popupManualClose = createAction('[Modal] manual close');\n","export enum ModalDismissReasons {\n BackdropClick,\n Esc,\n}\n","
\n
\n
\n
\n
\n \n
\n \n \n \n \n \n \n @if (showCloseIcon) {\n \n } @else {\n \n }\n \n \n \n @if (title) {\n
{{ title }}
\n }\n
\n
\n
\n
\n
\n","import { DOCUMENT, NgIf, NgIfContext } from '@angular/common';\nimport {\n AfterViewInit,\n Component,\n ElementRef,\n EventEmitter,\n HostBinding,\n HostListener,\n Inject,\n Input,\n OnDestroy,\n OnInit,\n Optional,\n Output,\n Renderer2,\n TemplateRef,\n inject,\n} from '@angular/core';\n\nimport { AutomationModule } from '@frontend/sports/automation/core-feature';\nimport { PopupsConfig, Sitecore } from '@frontend/sports/common/client-config-data-access';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { OptionalNonRootToken } from '@frontend/sports/host-app/sports-product/feature/non-root-token';\nimport { MODAL_WINDOW_CONFIG } from '@frontend/sports/modal/feature/provider';\nimport { Store } from '@ngrx/store';\n\nimport { ModalAnimationHelperService } from '../dialog/modal-animation-helper.service';\nimport { ModalGestureService, ModalGestureVerticalConfig, VERTICAL_GESTURE_CONFIG } from '../dialog/modal-gesture.service';\nimport { HORIZONTAL_GESTURE_CONFIG, ModalGestureHorizontalConfig, ModalHorizontalGestureService } from '../dialog/modal-horizontal-gesture.service';\nimport { popupManualClose } from './actions';\nimport { ModalOptions } from './modal';\nimport { ModalDismissReasons } from './modal-dismiss-reasons';\n\nexport enum PopupAction {\n Open = 'open',\n ManualClose = 'manualClose',\n CloseByNavigation = 'closeByNavigation',\n CloseByMybets = 'CloseByMybets',\n CloseByNextPopup = 'CloseByNextPopup',\n CloseByPan = 'CloseByPan',\n}\n\nexport interface PopupClosingResult {\n closeAction: PopupAction;\n}\n\nexport enum CloseButtonType {\n Icon = 'Icon',\n Button = 'Button',\n}\nexport interface GestureOptions {\n enabled?: boolean;\n headerSelector?: string;\n scrollTargetQuerySelector?: string;\n}\n\n@Component({\n selector: 'ms-modal-window',\n templateUrl: 'modal-window.html',\n standalone: true,\n imports: [NgIf, AutomationModule],\n providers: [\n ModalGestureService,\n ModalHorizontalGestureService,\n {\n provide: VERTICAL_GESTURE_CONFIG,\n useClass: ModalGestureVerticalConfig,\n },\n {\n provide: HORIZONTAL_GESTURE_CONFIG,\n useClass: ModalGestureHorizontalConfig,\n },\n ],\n})\nexport class ModalWindowComponent implements OnInit, AfterViewInit, OnDestroy {\n @HostBinding('attr.aria-labelledby')\n @Input()\n ariaLabelledBy: string;\n\n /**\n * Provide a template to be rendered inside the modal-header.\n *\n * @type {TemplateRef}\n * @memberof ModalOptions\n */\n @Input()\n headerTemplate: TemplateRef;\n\n slideOutFromTopToButtom = 'modal-slide-out-to-bottom';\n slideOutToRight = 'modal-slide-out-to-right';\n\n @Input() backdrop: boolean | string = true;\n @Input() centered: string;\n @Input() keyboard = true;\n @Input() size: string;\n @Input() windowClass: string;\n @Input() showDialogHeader?: boolean;\n @Input() title?: string;\n @Input() hideCloseButton?: boolean;\n @Input() showBackNav?: boolean;\n @Input() animatePopupDismiss = false;\n @Input() showCloseIcon = true;\n @Input() closePrevious?: boolean;\n @Input() gestureOptions: GestureOptions;\n\n @Output() dismiss = new EventEmitter<{ closeAction: PopupAction.ManualClose }>();\n\n @HostBinding('attr.role') role = 'dialog';\n @HostBinding('attr.tabindex') tabindex = '-1';\n\n @HostBinding()\n get class(): string {\n return 'modal ' + (this.windowClass ? ' ' + this.windowClass : '');\n }\n\n showCloseBtnText?: boolean;\n\n private readonly _doc = inject(DOCUMENT);\n\n constructor(\n public sitecore: Sitecore,\n private elementRef: ElementRef,\n private renderer: Renderer2,\n private timerService: TimerService,\n private store: Store,\n private modalGestureService: ModalGestureService,\n private modalAnimationHelper: ModalAnimationHelperService,\n private popUpsConfig: PopupsConfig,\n private modalHorizontalGestureService: ModalHorizontalGestureService,\n @Optional() @Inject(OptionalNonRootToken(MODAL_WINDOW_CONFIG)) private config: Partial | undefined,\n ) {\n if (this.config) {\n this.animatePopupDismiss = this.config.animatePopupDismiss ?? false;\n this.showCloseIcon = this.popUpsConfig.closeButtonType === CloseButtonType.Icon || (this.config.showCloseIcon ?? true);\n }\n }\n\n close(): void {\n if (this.gestureOptions.enabled) {\n this.modalAnimationHelper.addSlideWithScale();\n } else {\n this.renderer.addClass(this.elementRef.nativeElement, this.slideOutFromTopToButtom);\n }\n\n this.dismissPopup();\n\n this.store.dispatch(popupManualClose());\n }\n\n goBack(): void {\n this.renderer.addClass(this.elementRef.nativeElement, this.slideOutToRight);\n this.dismissPopup();\n }\n\n private dismissPopup() {\n if (this.animatePopupDismiss) {\n this.timerService.setTimeoutOutsideAngular(() => {\n this.dismiss.emit({ closeAction: PopupAction.ManualClose });\n }, 500);\n } else {\n this.dismiss.emit({ closeAction: PopupAction.ManualClose });\n }\n }\n\n @HostListener('mousedown', ['$event'])\n @HostListener('touch', ['$event'])\n backdropClick(event: Event): void {\n if (this.backdrop === true && this.elementRef.nativeElement === event.target) {\n this.dismissWindow(ModalDismissReasons.BackdropClick);\n }\n }\n\n //Prevent any scroll event within modal container to bubble up to body (preventing unwanted double scroll appearing)\n @HostListener('scroll', ['$event'])\n @HostListener('touchmove', ['$event'])\n preventScrolPropogation(event: Event) {\n event.stopPropagation();\n }\n\n @HostListener('keyup.esc', ['$event'])\n escKey(event: Event): void {\n if (this.keyboard && !event.defaultPrevented) {\n this.dismissWindow(ModalDismissReasons.Esc);\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private dismissWindow(reason: any): void {\n if (this.animatePopupDismiss) {\n this.renderer.addClass(this.elementRef.nativeElement, this.slideOutFromTopToButtom);\n this.timerService.setTimeoutOutsideAngular(() => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.dismiss.emit(reason);\n }, 500);\n } else {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.dismiss.emit(reason);\n }\n }\n\n ngOnInit(): void {\n this.renderer.addClass(this._doc.body, 'modal-open');\n }\n\n ngAfterViewInit(): void {\n this.timerService.setTimeoutOutsideAngular(() => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n if (!this.elementRef.nativeElement.contains(this._doc.activeElement)) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n this.elementRef.nativeElement['focus'].apply(this.elementRef.nativeElement, []);\n }\n });\n\n if (this.gestureOptions.headerSelector === 'event-details-modal') {\n this.modalHorizontalGestureService.addGestures(this.elementRef.nativeElement, this.gestureOptions);\n } else {\n this.modalGestureService.addGestures(this.elementRef.nativeElement, this.gestureOptions);\n }\n }\n\n ngOnDestroy(): void {\n this.renderer.removeClass(this._doc.body, 'modal-open');\n }\n}\n","import { InjectionToken, Injector, TemplateRef } from '@angular/core';\nimport { SafeHtml } from '@angular/platform-browser';\n\nimport { ScrollFreezeServiceTypes } from '@frontend/sports/common/core/feature/scroll-freeze';\nimport { ElementProvider } from '@frontend/sports/common/core/utils/element-provider';\nimport { TrackingType } from '@frontend/sports/tracking/feature';\nimport { DsButtonKind, DsButtonSize, DsButtonVariant } from '@frontend/ui/button';\n\nexport interface IDialogTracking {\n name: string;\n type: TrackingType;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data?: any;\n}\n\nexport interface ModalDialogOptions {\n data?: T;\n settings?: DialogSettings;\n}\n\nexport interface DialogSettings {\n isModalDialog?: boolean;\n isPopup?: boolean;\n showDialogHeader?: boolean;\n hideCloseButton?: boolean;\n title?: string;\n fit?: boolean;\n cssClass?: string;\n showPageHeader?: boolean;\n showBottomNav?: boolean;\n showBackNav?: boolean;\n animation?: DialogAnimation;\n closeAnimation?: DialogAnimation;\n tracking?: IDialogTracking;\n container?: HTMLElement;\n inColumn?: boolean;\n scrollFreezeType?: ScrollFreezeServiceTypes;\n backdropClass?: string;\n afterResulted?: Function;\n beforeDismiss?: () => boolean | Promise;\n scrollTargetQuerySelector?: string;\n closeOnChildPopup?: boolean;\n injector?: Injector;\n epcotShowBalance?: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n headerTemplate?: TemplateRef;\n showCloseBtnText?: boolean;\n closePrevious?: boolean;\n needsAuthentication?: boolean;\n animatePopupDismiss?: boolean;\n showCloseIcon?: boolean;\n headerSelector?: string;\n gestures?: boolean;\n}\n\nexport enum DialogAnimation {\n SlideInFromBottom = 'SlideInFromBottom',\n SlideInFromRight = 'SlideInFromRight',\n SlideOutFromTop = 'SlideOutFromTop',\n}\n\nexport interface ContentData {\n type?: DialogType;\n title?: string;\n firstParagraph?: string | SafeHtml;\n secondParagraph?: string | SafeHtml;\n buttons?: DialogButton[];\n showCloseLink?: boolean;\n}\n\nexport enum DialogType {\n Warning = 'warning',\n Info = 'info',\n Action = 'action',\n}\n\nexport interface DialogButton {\n text: string;\n type: ButtonType;\n buttonConfig?: {\n kind: DsButtonKind;\n variant: DsButtonVariant;\n size: DsButtonSize;\n };\n cssClass?: string;\n}\n\nexport enum DialogClosedBy {\n Button,\n CloseLink,\n}\n\nexport enum ButtonType {\n Ok,\n Cancel,\n}\n\nexport const MODAL_DIALOG_CONTAINER = new InjectionToken('ms-modal-dialog-container');\nexport const DEFAULT_HEADER_SELECTOR = '#headerWrapper';\n","import { Component, HostBinding, Input } from '@angular/core';\n\n@Component({\n selector: 'ms-modal-backdrop',\n template: '',\n standalone: true,\n})\nexport class ModalBackdropComponent {\n @Input() backdropClass: string;\n @HostBinding()\n get class(): string {\n return 'modal-backdrop ' + (this.backdropClass ? ' ' + this.backdropClass : '');\n }\n}\n","import { ChangeDetectionStrategy, Component, HostBinding } from '@angular/core';\n\n@Component({\n selector: 'ms-modal-cover',\n template: '',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ModalCoverComponent {\n @HostBinding('class') className = 'modal-gestures-cover';\n}\n","import { ComponentType, OverlayContainer } from '@angular/cdk/overlay';\nimport { ApplicationRef, ComponentRef, EnvironmentInjector, Injectable, Injector, StaticProvider, createComponent } from '@angular/core';\n\nimport { AppConfig } from '@frontend/sports/common/client-config-data-access';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\n\nimport { ModalAnimationHelperService } from '../dialog/modal-animation-helper.service';\nimport { ModalOptions } from './modal';\nimport { ModalBackdropComponent } from './modal-backdrop.component';\nimport { ModalCoverComponent } from './modal-cover.component';\nimport { ActiveModal, ContentRef, ModalRef } from './modal-ref';\nimport { ModalWindowComponent } from './modal-window.component';\n\n@Injectable({ providedIn: 'root' })\nexport class ModalStack {\n private attributes: Extract[] = [\n 'ariaLabelledBy',\n 'backdrop',\n 'centered',\n 'keyboard',\n 'size',\n 'windowClass',\n 'showDialogHeader',\n 'title',\n 'hideCloseButton',\n 'epcotShowBalance',\n 'headerTemplate',\n 'showCloseBtnText',\n 'showBackNav',\n 'closePrevious',\n 'animatePopupDismiss',\n 'showCloseIcon',\n 'gestureOptions',\n ];\n\n private backdropAttributes: (keyof ModalOptions)[] = ['backdropClass'];\n\n constructor(\n private timerService: TimerService,\n private applicationRef: ApplicationRef,\n private injector: EnvironmentInjector,\n private containerService: OverlayContainer,\n private appConfig: AppConfig,\n private modalAnimationHelper: ModalAnimationHelperService,\n ) {}\n\n open(contentInjector: Injector, content: ComponentType, options: ModalOptions): ModalRef {\n const containerEl = options.container ?? this.containerService.getContainerElement();\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!containerEl) {\n throw new Error(`The specified modal container \"${options.container?.tagName || 'body'}\" was not found in the DOM.`);\n }\n\n if (options.centerVerticalToViewport) {\n // eslint-disable-next-line no-param-reassign\n options.windowClass = `${options.windowClass ?? ''} modal-vertical-align`;\n }\n\n const activeModal = new ActiveModal();\n const contentRef = this._getContentRef(options.injector || contentInjector, content, activeModal);\n\n const coverCmptRef = options.gestureOptions?.enabled ? this._attachCoverElement(containerEl) : undefined;\n const backdropCmptRef = options.backdrop !== false ? this._attachBackdrop(containerEl) : undefined;\n const windowCmptRef = this._attachWindowComponent(containerEl, contentRef);\n const modalRef = new ModalRef(windowCmptRef, contentRef, backdropCmptRef, coverCmptRef, options.beforeDismiss);\n activeModal.result = modalRef.result;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n activeModal.close = (result?: any) => {\n if (!options.closeAnimation) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n modalRef.close(result);\n return;\n }\n if (\n options.closeAnimation &&\n backdropCmptRef?.instance?.class?.includes('close-animation') &&\n !this.appConfig.modalDialogGesturesEnabled\n ) {\n modalRef.addBackdropClass('hide-backdrop');\n }\n if (this.appConfig.modalDialogGesturesEnabled && windowCmptRef.instance.gestureOptions?.enabled) {\n this.modalAnimationHelper.addSlideWithScale();\n } else {\n windowCmptRef.instance.windowClass += ` ${options.closeAnimation}`;\n }\n this.timerService.setTimeout(() => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n modalRef.close(result);\n }, 350);\n };\n activeModal.dismiss = (reason: unknown) => {\n modalRef.dismiss(reason);\n };\n\n this._applyWindowOptions(windowCmptRef.instance, options);\n if (options.container && options.container === containerEl && options.centerVerticalToViewport) {\n this._applyVerticalCentering(windowCmptRef, containerEl);\n }\n\n if (backdropCmptRef?.instance) {\n this._applyBackdropOptions(backdropCmptRef.instance, options);\n }\n\n return modalRef;\n }\n\n private _attachBackdrop(containerEl: HTMLElement): ComponentRef {\n const backdropCmptRef = createComponent(ModalBackdropComponent, { environmentInjector: this.injector });\n this.applicationRef.attachView(backdropCmptRef.hostView);\n containerEl.append(backdropCmptRef.location.nativeElement as HTMLElement);\n\n return backdropCmptRef;\n }\n\n private _attachWindowComponent(containerEl: HTMLElement, contentRef: ContentRef): ComponentRef {\n const windowCmptRef = createComponent(ModalWindowComponent, {\n environmentInjector: this.injector,\n projectableNodes: contentRef.nodes,\n });\n this.applicationRef.attachView(windowCmptRef.hostView);\n containerEl.append(windowCmptRef.location.nativeElement as Node);\n\n return windowCmptRef;\n }\n\n private _attachCoverElement(containerEl: HTMLElement): ComponentRef {\n const coverCmptRef = createComponent(ModalCoverComponent, { environmentInjector: this.injector });\n this.applicationRef.attachView(coverCmptRef.hostView);\n containerEl.append(coverCmptRef.location.nativeElement as HTMLElement);\n\n return coverCmptRef;\n }\n\n private _applyWindowOptions(windowInstance: ModalWindowComponent, options: ModalOptions): void {\n this.attributes.forEach((optionName) => {\n if (Object.prototype.hasOwnProperty.call(options, optionName)) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n //@ts-ignore ignore below accessor\n // eslint-disable-next-line no-param-reassign\n windowInstance[optionName] = options[optionName];\n }\n });\n }\n\n private _applyVerticalCentering(windowCmptRef: ComponentRef, containerEl: HTMLElement): void {\n const domEl = windowCmptRef.location.nativeElement as HTMLElement;\n const windowHeight = window.innerHeight;\n\n let recalculateTimes = 0; // the modal content can be async so let's try a few times to position the modal.\n\n const repositionModal = () => {\n const containerTopOffset = containerEl.getBoundingClientRect().top;\n const newHeight = domEl.clientHeight;\n const newTop = (windowHeight - containerTopOffset) / 2 - newHeight / 2;\n domEl.style.top = `${newTop}px`;\n recalculateTimes++;\n\n if (recalculateTimes >= 3 || newHeight >= 100) {\n // if the height is greater than 100 most likely the content is loaded\n return;\n }\n\n setTimeout(repositionModal, recalculateTimes * 10);\n };\n\n setTimeout(repositionModal, recalculateTimes * 10);\n }\n\n private _applyBackdropOptions(backdropInstance: ModalBackdropComponent, options: ModalOptions): void {\n this.backdropAttributes.forEach((optionName) => {\n if (options[optionName]) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n //@ts-ignore dynamic accessor\n // eslint-disable-next-line no-param-reassign\n backdropInstance[optionName] = options[optionName];\n }\n });\n }\n\n private _getContentRef(contentInjector: Injector, content: ComponentType, context: ActiveModal): ContentRef {\n return this._createFromComponent(contentInjector, content, context);\n }\n\n private _createFromComponent(contentInjector: Injector, content: ComponentType, context: ActiveModal): ContentRef {\n const providers: StaticProvider[] = [{ provide: ActiveModal, useValue: context }];\n\n const modalContentInjector = Injector.create({\n providers,\n parent: contentInjector,\n });\n\n const componentRef = createComponent(content, { environmentInjector: this.injector, elementInjector: modalContentInjector });\n this.applicationRef.attachView(componentRef.hostView);\n\n return new ContentRef([[componentRef.location.nativeElement]], componentRef.hostView, componentRef);\n }\n}\n","import { ComponentType } from '@angular/cdk/overlay';\nimport { NgIfContext } from '@angular/common';\nimport { Injectable, Injector, TemplateRef } from '@angular/core';\n\nimport { ModalRef } from './modal-ref';\nimport { ModalStack } from './modal-stack';\nimport { GestureOptions } from './modal-window.component';\n\n/**\n * Represent options available when opening new modal windows.\n */\nexport interface ModalOptions {\n /**\n * Sets the aria attribute aria-labelledby to a modal window.\n *\n * @since 2.2.0\n */\n ariaLabelledBy?: string;\n\n /**\n * Whether a backdrop element should be created for a given modal (true by default).\n * Alternatively, specify 'static' for a backdrop which does not close the modal on click.\n */\n backdrop?: boolean | 'static';\n\n /**\n * Function called when a modal will be dismissed.\n * If this function returns false, the promise is resolved with false or the promise is rejected, the modal is not\n * dismissed.\n */\n beforeDismiss?: () => boolean | Promise;\n\n /**\n * To center the modal vertically (false by default).\n *\n * @since 1.1.0\n */\n centered?: boolean;\n\n /**\n * An element to which to attach newly opened modal windows.\n */\n container?: HTMLElement;\n\n /**\n * Injector to use for modal content.\n */\n injector?: Injector;\n\n /**\n * Whether to close the modal when escape key is pressed (true by default).\n */\n keyboard?: boolean;\n\n /**\n * Size of a new modal window.\n */\n size?: 'sm' | 'lg';\n\n /**\n * Custom class to append to the modal window\n */\n windowClass?: string;\n\n /**\n * Custom class to append to the modal backdrop\n *\n * @since 1.1.0\n */\n backdropClass?: string;\n\n /**\n * Force the modal window to center vertically relative to the viewport (for scrollable containers).\n * Assumes positon absolute for the modal. Can be used only when `container` is set.\n *\n * @type {boolean}\n * @memberof ModalOptions\n */\n centerVerticalToViewport?: boolean;\n\n showDialogHeader?: boolean;\n\n title?: string;\n\n hideCloseButton?: boolean;\n\n closeAnimation?: string;\n\n epcotShowBalance?: boolean;\n\n showBackNav?: boolean;\n /**\n * Provide a template to be rendered inside the modal-header.\n *\n * @type {TemplateRef}\n * @memberof ModalOptions\n */\n headerTemplate?: TemplateRef;\n\n showCloseBtnText?: boolean;\n /**\n * To close the previosly opened modal window\n */\n closePrevious?: boolean;\n\n /**\n * Used to delay the dismissal of the popup and animate it\n */\n animatePopupDismiss?: boolean;\n\n /**\n * Used to show a close icon instead of close button\n */\n showCloseIcon?: boolean;\n\n /**\n * Provides required options for gestures to work\n */\n gestureOptions?: GestureOptions;\n}\n\n/**\n * A service to open modal windows. Creating a modal is straightforward: create a template and pass it as an argument to\n * the \"open\" method!\n */\n@Injectable({ providedIn: 'root' })\nexport class Modal {\n constructor(\n private _injector: Injector,\n private _modalStack: ModalStack,\n ) {}\n\n /**\n * Opens a new modal window with the specified content and using supplied options. Content can be provided\n * as a TemplateRef or a component type. If you pass a component type as content than instances of those\n * components can be injected with an instance of the ActiveModal class. You can use methods on the\n * ActiveModal class to close / dismiss modals from \"inside\" of a component.\n */\n open(content: ComponentType, options: ModalOptions = {}): ModalRef {\n return this._modalStack.open(this._injector, content, options);\n }\n}\n","
\n @if (shouldCloseLinkBeDisplayed) {\n \n }\n

{{ contentData.title }}

\n\n
\n

\n

\n\n @if (isDesignSystem) {\n
\n @for (button of contentData.buttons; track $index) {\n \n {{ button.text }}\n \n }\n
\n } @else {\n
\n @for (button of contentData.buttons; track $index) {\n \n }\n
\n }\n
\n
\n","import { NgFor, NgIf } from '@angular/common';\nimport { Component } from '@angular/core';\n\nimport { DsButton } from '@frontend/ui/button';\n\nimport { ActiveModal } from '../common/modal-ref';\nimport { ButtonType, ContentData, DialogButton } from './modal-dialog.model';\n\n@Component({\n selector: 'ms-message-dialog',\n templateUrl: 'message-dialog.html',\n standalone: true,\n imports: [NgIf, NgFor, DsButton],\n})\nexport class MessageDialogComponent {\n private _contentData: ContentData = {};\n\n get isDesignSystem(): boolean {\n return this._contentData.buttons?.every((b) => b.buttonConfig) ?? false;\n }\n\n constructor(private activeModal: ActiveModal) {}\n\n get contentData(): ContentData {\n return this._contentData;\n }\n\n set contentData(data: ContentData) {\n this._contentData = data;\n }\n\n get shouldCloseLinkBeDisplayed(): boolean {\n return Boolean(this._contentData.showCloseLink);\n }\n\n closeLinkClickHandler(): void {\n this.dismiss();\n }\n\n buttonClickHandler(activatedButton: DialogButton): void {\n this.close(activatedButton.type);\n }\n\n private close(result: ButtonType): void {\n this.activeModal.close(result);\n }\n\n private dismiss(): void {\n this.activeModal.dismiss();\n }\n}\n","/* eslint-disable\n @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call\n*/\nimport {\n AfterViewInit,\n ChangeDetectorRef,\n Component,\n ComponentRef,\n DestroyRef,\n ElementRef,\n HostBinding,\n OnDestroy,\n Type,\n ViewChild,\n ViewContainerRef,\n inject,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { TrackingService, TrackingType, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { ReplaySubject, combineLatest, isObservable } from 'rxjs';\nimport { first } from 'rxjs/operators';\n\nimport { ModalRef } from '../common/modal-ref';\nimport { DialogSettings } from './modal-dialog.model';\nimport { OpenedModalsStorageService } from './opened-modals-storage.service';\n\n@Component({\n selector: 'ms-modal-dialog',\n templateUrl: 'modal-dialog.html',\n providers: [ScrollContainerService],\n standalone: true,\n})\nexport class ModalDialogComponent implements AfterViewInit, OnDestroy {\n private destroyRef = inject(DestroyRef);\n\n @HostBinding('class') className = 'ds-scrollbar';\n\n @ViewChild('componentHost', { read: ViewContainerRef }) componentHost: ViewContainerRef;\n dialogSettings: DialogSettings = {};\n\n contentComponent: Type;\n inputs: any;\n modalRef: ModalRef;\n\n private componentLoaded$ = new ReplaySubject>(1);\n\n constructor(\n private trackingService: TrackingService,\n private scrollContainer: ScrollContainerService,\n private _elRef: ElementRef,\n private cdr: ChangeDetectorRef,\n private openedModalsStorage: OpenedModalsStorageService,\n ) {}\n\n ngAfterViewInit(): void {\n this.mountComponent();\n if (this.modalRef) {\n combineLatest([this.componentLoaded$, this.modalRef.newData$])\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(([componentRef, data]) => {\n for (const [key, value] of Object.entries(data)) {\n componentRef.setInput(key, value);\n }\n this.cdr.detectChanges();\n });\n }\n if (this.dialogSettings.tracking) {\n switch (this.dialogSettings.tracking.type) {\n case TrackingType.PageName:\n this.trackingService.track(this.dialogSettings.tracking.name, this.dialogSettings.tracking.data || {});\n break;\n case TrackingType.Referring:\n this.trackingService.update({ [trackingConstants.PAGE_REFERRING_ACTION]: this.dialogSettings.tracking.name });\n break;\n }\n }\n\n this.componentLoaded$.pipe(first()).subscribe(() => {\n const target = this._elRef.nativeElement.querySelector(this.dialogSettings.scrollTargetQuerySelector);\n if (target) {\n this.scrollContainer.setTarget(target);\n }\n this.openedModalsStorage.openedModal = this.modalRef;\n });\n }\n\n private mountComponent = () => {\n this.componentHost.clear();\n const componentRef = this.componentHost.createComponent(this.contentComponent);\n if (this.inputs) {\n Object.keys(this.inputs).forEach((key) => {\n componentRef.setInput(key, this.inputs[key]);\n });\n componentRef.changeDetectorRef.detectChanges();\n }\n\n if (componentRef.instance.componentLoaded && isObservable(componentRef.instance.componentLoaded)) {\n componentRef.instance.componentLoaded.pipe(first()).subscribe(() => this.componentLoaded$.next(componentRef));\n } else {\n this.componentLoaded$.next(componentRef);\n }\n };\n\n ngOnDestroy(): void {\n this.openedModalsStorage.removeCurrent(this.modalRef);\n }\n}\n","
\n","import { Injectable } from '@angular/core';\n\nimport { NativeAppService } from '@frontend/vanilla/core';\n\nexport interface SensitivePage {\n isSensitiveForPushNotifications: string;\n isSensitiveForSliderGames: string;\n action: string;\n id: string;\n type: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ModalEventBridgeService {\n private _eventName = 'SensitivePage';\n constructor(private nativeApp: NativeAppService) {}\n\n notifyOpenOverlay(): void {\n this.notifySensitivePage({\n isSensitiveForPushNotifications: 'false',\n isSensitiveForSliderGames: 'false',\n action: 'open',\n id: '',\n type: 'overlay',\n });\n }\n\n notifyCloseOverlay(): void {\n this.notifySensitivePage({\n isSensitiveForPushNotifications: 'false',\n isSensitiveForSliderGames: 'false',\n action: 'close',\n id: '',\n type: 'overlay',\n });\n }\n\n notifySensitivePage(page: SensitivePage): void {\n this.nativeApp.sendToNative({\n eventName: this._eventName,\n parameters: page,\n });\n }\n}\n","import { ComponentType } from '@angular/cdk/overlay';\nimport { Injectable, Type, inject } from '@angular/core';\n\nimport { AppConfig } from '@frontend/sports/common/client-config-data-access';\nimport { ScrollFreezeService, ScrollFreezeServiceTypes } from '@frontend/sports/common/core/feature/scroll-freeze';\nimport { DeviceService, SessionStoreService } from '@frontend/vanilla/core';\nimport { extend } from 'lodash-es';\n\nimport { Modal } from '../common/modal';\nimport { ModalRef } from '../common/modal-ref';\nimport { MessageDialogComponent } from './message-dialog.component';\nimport { ModalDialogComponent } from './modal-dialog.component';\nimport { ContentData, DialogAnimation, DialogSettings, ModalDialogOptions } from './modal-dialog.model';\nimport { ModalEventBridgeService } from './modal-event-bridge.service';\n\n@Injectable({ providedIn: 'root' })\nexport class ModalDialogService {\n static get DefaultSettings(): DialogSettings {\n return {\n isModalDialog: true,\n showDialogHeader: false,\n showPageHeader: false,\n fit: true,\n hideCloseButton: false,\n };\n }\n\n private openModalsCountKey = 'openedSportsModalDialogsCount';\n private animationClasses: Record = {\n [DialogAnimation.SlideInFromBottom]: 'modal-slide-from-bottom',\n [DialogAnimation.SlideInFromRight]: 'modal-slide-from-right',\n [DialogAnimation.SlideOutFromTop]: 'modal-slide-out-to-bottom',\n };\n\n private modal: Modal = inject(Modal);\n private scrollFreeze: ScrollFreezeService = inject(ScrollFreezeService);\n private eventBridge: ModalEventBridgeService = inject(ModalEventBridgeService);\n private deviceService: DeviceService = inject(DeviceService);\n private sessionStorage: SessionStoreService = inject(SessionStoreService);\n private appConfig: AppConfig = inject(AppConfig);\n\n private get openedModalsCount(): number {\n return this.sessionStorage.get(this.openModalsCountKey) ?? 0;\n }\n\n private increaseModalsCount(): void {\n this.sessionStorage.set(this.openModalsCountKey, this.openedModalsCount + 1);\n }\n\n private decreaseModalsCount(): void {\n this.sessionStorage.set(this.openModalsCountKey, this.openedModalsCount - 1);\n }\n\n isModalDialogOpen(): boolean {\n return this.openedModalsCount > 0;\n }\n\n openPopup(component: Type, optionsParam?: ModalDialogOptions>): ModalRef {\n let options = optionsParam ?? {};\n options.settings = options.settings ?? {};\n options.settings.isPopup = true;\n\n return this.openDialog(component, options);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n openDialog(component: Type, optionsParam?: ModalDialogOptions | void>): ModalRef {\n let options = optionsParam ?? {};\n const defaultSettings = extend({}, ModalDialogService.DefaultSettings, { fit: !options.settings?.isPopup });\n const modalRef = this.openModal(ModalDialogComponent, defaultSettings, options.settings);\n\n const dialogInstance = modalRef.componentInstance as ModalDialogComponent;\n dialogInstance.contentComponent = component;\n dialogInstance.inputs = options.data;\n dialogInstance.modalRef = modalRef;\n dialogInstance.dialogSettings = extend({}, ModalDialogService.DefaultSettings, options.settings);\n\n return modalRef;\n }\n\n openMessage(contentData: ContentData, settings?: DialogSettings): ModalRef {\n const defaultSettings = extend({}, ModalDialogService.DefaultSettings, { showDialogHeader: false });\n const modalRef = this.openModal(MessageDialogComponent, defaultSettings, settings);\n\n const dialogInstance = modalRef.componentInstance as MessageDialogComponent;\n dialogInstance.contentData = contentData;\n\n return modalRef;\n }\n\n // eslint-disable-next-line max-lines-per-function\n private openModal>(\n component: TComponent,\n defaultSettings: DialogSettings,\n settings?: DialogSettings,\n ): ModalRef {\n const copySettings = extend({}, defaultSettings, settings);\n const modalCss = this.buildModalCss(copySettings, settings?.isPopup);\n const modalBackdropCss = this.buildModalBackdropCss(copySettings, settings?.isPopup);\n\n if (copySettings.closeAnimation) {\n copySettings.closeAnimation = this.animationClasses[copySettings.closeAnimation] as DialogAnimation;\n }\n\n const modalRef = this.modal.open(component, {\n windowClass: modalCss,\n backdropClass: modalBackdropCss,\n backdrop: copySettings.isModalDialog ? 'static' : true,\n keyboard: !copySettings.isModalDialog,\n container: copySettings.container,\n centerVerticalToViewport: !!copySettings.container,\n beforeDismiss: copySettings.beforeDismiss,\n showDialogHeader: copySettings.showDialogHeader,\n hideCloseButton: copySettings.hideCloseButton,\n title: copySettings.title,\n closeAnimation: copySettings.closeAnimation,\n injector: copySettings.injector,\n epcotShowBalance: copySettings.epcotShowBalance,\n headerTemplate: copySettings.headerTemplate,\n showCloseBtnText: copySettings.showCloseBtnText,\n showBackNav: copySettings.showBackNav,\n closePrevious: copySettings.closePrevious,\n ...(copySettings.animatePopupDismiss === undefined ? {} : { animatePopupDismiss: copySettings.animatePopupDismiss }),\n ...(copySettings.showCloseIcon === undefined ? {} : { showCloseIcon: copySettings.showCloseIcon }),\n gestureOptions: {\n enabled: copySettings.gestures && this.appConfig.modalDialogGesturesEnabled,\n headerSelector: copySettings.headerSelector,\n scrollTargetQuerySelector: copySettings.scrollTargetQuerySelector,\n },\n });\n\n if (!this.openedModalsCount) {\n this.eventBridge.notifyOpenOverlay();\n }\n this.increaseModalsCount();\n if (copySettings.scrollFreezeType !== ScrollFreezeServiceTypes.None && this.deviceService.isMobile) {\n this.scrollFreeze.freezeScroll(copySettings.scrollFreezeType);\n }\n modalRef.result.finally(() => {\n if (copySettings.scrollFreezeType !== ScrollFreezeServiceTypes.None && this.deviceService.isMobile) {\n this.scrollFreeze.releaseScroll(copySettings.scrollFreezeType);\n }\n this.decreaseModalsCount();\n if (!this.openedModalsCount) {\n this.eventBridge.notifyCloseOverlay();\n }\n\n copySettings.afterResulted?.();\n });\n\n return modalRef;\n }\n\n private buildModalBackdropCss(settings: DialogSettings, isPopup?: boolean): string {\n const cssClasses = [];\n\n if (!isPopup) {\n cssClasses.push('modal-dialog-backdrop');\n }\n\n if (settings.showPageHeader) {\n cssClasses.push('modal-under-header');\n }\n\n if (settings.showBottomNav) {\n cssClasses.push('modal-above-footer');\n }\n\n if (settings.inColumn) {\n cssClasses.push('modal-dialog-backdrop-in-column');\n }\n\n if (settings.closeAnimation) {\n cssClasses.push('close-animation');\n }\n\n if (settings.backdropClass) {\n cssClasses.push(settings.backdropClass);\n }\n\n return cssClasses.filter(Boolean).join(' ');\n }\n\n private buildModalCss(settings: DialogSettings, isPopup?: boolean): string {\n const cssClasses = [settings.cssClass];\n\n if (!isPopup) {\n cssClasses.push('modal-dialog-wrapper');\n }\n\n if (settings.fit) {\n cssClasses.push('modal-fit');\n }\n\n if (settings.showPageHeader) {\n cssClasses.push('modal-under-header');\n }\n\n if (settings.showBottomNav) {\n cssClasses.push('modal-above-footer');\n }\n\n if (settings.animation) {\n cssClasses.push(this.animationClasses[settings.animation]);\n }\n\n if (settings.inColumn) {\n cssClasses.push('modal-dialog-in-column');\n }\n\n return cssClasses.filter(Boolean).join(' ');\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { EpcotConfig } from '@frontend/sports/common/client-config-data-access';\n\n/**\n * @deprecated This service is deprecated. Please use {@link EpcotConfig} directly instead.\n */\n@Injectable({ providedIn: 'root' })\nexport class EpcotConfigService {\n constructor(private epcotConfig: EpcotConfig) {}\n\n /**\n * @deprecated This method is deprecated. Epcot modules are not used in production anymore. Please use {@link EpcotConfig.isEnabled} directly instead.\n */\n isEnabled(module?: EpcotModule): boolean {\n return this.epcotConfig.isEnabled && (!module || this.epcotConfig.modules.includes(module));\n }\n\n get isShowMyBetsPopup(): boolean {\n return this.isEnabled(EpcotModule.MyBetsBetSlip);\n }\n}\n\nexport enum EpcotModule {\n QuickBet = 'quickbet',\n OptionPanel = 'optionpanel',\n Scoreboard = 'scoreboard',\n Betslip = 'betslip',\n MyBetsBetSlip = 'mybetsbetslip',\n Search = 'search',\n Teampages = 'teampages',\n Rewards = 'rewards',\n Statsindicator = 'statsindicator',\n Favorites = 'favorites',\n Errors = 'errors',\n TopEvents = 'topevents',\n BetGenerator = 'parlaygenerator',\n Grid = 'grid',\n EventDetails = 'eventdetails',\n PopularBets = 'popularbets',\n}\n","import { Injectable } from '@angular/core';\n\nimport { Observable, Subject } from 'rxjs';\n\n/** @stable */\n@Injectable({\n providedIn: 'root',\n})\nexport class HeaderSearchService {\n private events: Subject = new Subject();\n\n /** Observable of when the header search input is clicked. */\n get clickEvent(): Observable {\n return this.events.pipe();\n }\n\n click() {\n this.events.next();\n }\n}\n","import { FixturePage } from '@cds/betting-offer';\nimport { DayOfRaceDate, TimeGroupedMeetings } from '@cds/betting-offer/domain-specific/horse-racing';\nimport { Meeting } from '@cds/betting-offer/tags';\nimport { DomainEvent } from '@frontend/sports/common/core/utils/dispatcher';\n\n/* eslint-disable @typescript-eslint/naming-convention,no-underscore-dangle,id-blacklist,id-match */\nexport class Events {\n static Grid = class {\n static EventsAddedToGrid = new DomainEvent('EVENTS_ADDED_TO_GRID');\n };\n\n static Slideshow = class {\n static SlideInit = new DomainEvent('SLIDE_INIT');\n static SlideSet = new DomainEvent('SLIDE_SET');\n };\n\n static EventPush = class {\n static EventUpdated = new DomainEvent('EVENT_UPDATED');\n static MarketUpdated = new DomainEvent('MARKET_UPDATED');\n static EventLastUpdated = new DomainEvent('EVENT_LASTUPDATED');\n };\n\n static PageRequiresNoAnimation = new DomainEvent('PAGE_REQUIRES_NO_ANIMATION');\n static ClaimsChanged = new DomainEvent('CLAIMS_CHANGED');\n\n static HorseRacing = class {\n static OpenMeetingSelector = new DomainEvent<{\n meetings: TimeGroupedMeetings;\n dayOfRace: DayOfRaceDate;\n meeting: Meeting;\n futureMeetings: FixturePage;\n }>('OPEN_HORSE_MEETING_SELECTOR');\n\n static CloseMeetingSelector = new DomainEvent('CLOSE_HORSE_MEETING_SELECTOR');\n static MeetingSelected = new DomainEvent<{ meeting: Meeting; dayOfRaceDate: DayOfRaceDate }>('MEETING_SELECTED');\n static FixtureSelectTopTwo = new DomainEvent('FIXTURE_SELECT_TOPTWO');\n static FixtureSelectTopThree = new DomainEvent('FIXTURE_SELECT_TOPTHREE');\n };\n\n // static BetFinder = class {\n // static FieldFocus = new DomainEvent('ON_BETFINDER_FIELD_FOCUS');\n // static FieldBlur = new DomainEvent('ON_BETFINDER_FIELD_BLUR');\n // };\n\n static BetslipPluginStateChanged = new DomainEvent('BETSLIP_PLUGIN_STATE_CHANGED');\n static ClosePluginSwitcher = new DomainEvent('CLOSE_PLUGIN_SWITCHER');\n\n static Favourites = class {\n static FavouritesChanged = new DomainEvent('FAVOURITES_CHANGED');\n };\n\n static Popup = class {\n static PopupClosed = new DomainEvent('POPUP_CLOSED');\n static BeforePopupDisplayed = new DomainEvent('BEFORE_POPUP_DISPLAYED');\n };\n\n static UnauthenticatedCallEarlyPayoutSubscription = new DomainEvent('UNAUTHENTICATED_EARLY_PAYOUT');\n static EarlyPayoutSuccess = new DomainEvent('EARLY_PAYOUT_SUCCESS');\n static OverlayToggle = new DomainEvent('OVERLAY_TOGGLE');\n static LeagueHeaderToggle = new DomainEvent('LEAGUE_HEADER_TOGGLE');\n\n static RightSwipe = new DomainEvent('RIGHT_SWIPE');\n static M2UserClaimsChanged = new DomainEvent('m2-user-claimsChanged');\n static PageVisibilityChange = new DomainEvent('PAGE_VISIBILITY_CHANGE');\n static OrientationChanged = new DomainEvent('ORIENTATION_CHANGED');\n}\n/* eslint-enable @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match */\n","import { Injectable } from '@angular/core';\n\nimport { ModalEventBridgeService } from '@frontend/sports/modal/feature';\n\nenum PopupAction {\n Open = 'open',\n Close = 'close',\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SensitivePageTrackerService {\n constructor(private eventBridgeService: ModalEventBridgeService) {}\n\n dispatchSensitivePagesEvent(currentPopup: string | null, oldPopup?: string): void {\n if (oldPopup) {\n this.popupClosedNotification(oldPopup);\n }\n if (currentPopup) {\n this.popupOpenedNotification(currentPopup);\n }\n }\n\n private popupClosedNotification(popupName: string): void {\n this.popupNotification(this.isPopupSensitive(popupName, PopupAction.Close), PopupAction.Close, popupName);\n }\n\n private popupOpenedNotification(popupName: string): void {\n this.popupNotification(this.isPopupSensitive(popupName, PopupAction.Open), PopupAction.Open, popupName);\n }\n\n private isPopupSensitive(popupName: string, action: PopupAction): string {\n return (popupName === 'betslip' && action === PopupAction.Open).toString();\n }\n\n private popupNotification(popupSensitivity: string, popupAction: PopupAction, popupId: string): void {\n this.eventBridgeService.notifySensitivePage({\n isSensitiveForPushNotifications: popupSensitivity,\n isSensitiveForSliderGames: popupSensitivity,\n action: popupAction,\n id: popupId,\n type: 'popup',\n });\n }\n}\n","import { DOCUMENT, Location } from '@angular/common';\nimport { Injectable, inject } from '@angular/core';\nimport { Router, RouterEvent } from '@angular/router';\n\nimport { AppConfig, EventDetailsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { DispatcherService } from '@frontend/sports/common/core/utils/dispatcher';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { TimerService } from '@frontend/sports/common/core/utils/timer';\nimport { filterSports, filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport {\n DialogAnimation,\n ModalAnimationHelperService,\n ModalDialogOptions,\n ModalDialogService,\n ModalDismissReasons,\n ModalRef,\n OpenedModalsStorageService,\n PopupAction,\n PopupClosingResult,\n} from '@frontend/sports/modal/feature';\nimport { TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { UserService } from '@frontend/sports/user/feature';\nimport {\n HtmlNode,\n LocationChangeEvent,\n MediaQueryService,\n MenuAction,\n MenuActionsService,\n NavigationService,\n UrlService,\n WINDOW,\n} from '@frontend/vanilla/core';\nimport { HeaderSearchService } from '@frontend/vanilla/shared/header';\nimport { extend, isFunction } from 'lodash-es';\nimport { Observable, Subject } from 'rxjs';\nimport { distinctUntilKeyChanged, filter, map } from 'rxjs/operators';\n\nimport { EpcotConfigService } from '../common/epcot-config.service';\nimport { NavigationHelper } from '../navigation-core/navigation-helper.service';\nimport { RedirectHelperService } from '../navigation-core/redirect-helper.service';\nimport { ModelPopupTypes, UrlHelperService } from '../navigation-core/url-helper.service';\nimport { Events as RacingEvents } from '../racing/models/events';\nimport { SensitivePageTrackerService } from './sensitive-page-tracking.service';\n\nexport type PopupTypes =\n | 'betfinder'\n | 'betslip'\n | 'update-mobile-number'\n | 'my-bets'\n | 'sports'\n | 'grouped-sports'\n | 'archived-bets'\n | 'bet-generator'\n | 'transaction-bets'\n | 'bet-builder-overlay'\n | 'hr-help-overlay'\n | 'hr-info-overlay'\n | 'gr-help-overlay'\n | 'gr-info-overlay'\n | 'event-switcher'\n | 'ngrx-reports'\n | 'odds-change-preference';\n\ninterface PopupStack {\n name: PopupTypes;\n handle: ModalRef;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class PopupManager {\n currentPopup: PopupTypes | null = null;\n fullReloadRequired: boolean;\n private popupStack: PopupStack[] = [];\n private largeScreen = false;\n private popups: { [key: string]: { component: any; options: ModalDialogOptions } } = {};\n private _setLastActiveItem = new Subject();\n private opening: boolean;\n\n private readonly _doc = inject(DOCUMENT);\n\n constructor(\n private dialogService: ModalDialogService,\n private sensitivePageTracker: SensitivePageTrackerService,\n private htmlNode: HtmlNode,\n private tracker: TrackingService,\n private dispatcher: DispatcherService,\n private navigationService: NavigationService,\n private location: Location,\n private router: Router,\n private routerEventsService: RouterEventsService,\n private urlService: UrlService,\n private urlHelperService: UrlHelperService,\n private mediaService: MediaQueryService,\n private timerService: TimerService,\n private headerSearchService: HeaderSearchService,\n private epcotConfigService: EpcotConfigService,\n private redirectHelper: RedirectHelperService,\n private userService: UserService,\n private menuActionsService: MenuActionsService,\n private navigationHelper: NavigationHelper,\n private appConfig: AppConfig,\n private modalAnimationHelper: ModalAnimationHelperService,\n private openedModalsStorage: OpenedModalsStorageService,\n private eventDetailsConfig: EventDetailsConfig,\n ) {\n this.navigationService.locationChange.pipe(filterSports()).subscribe((e: LocationChangeEvent) => this.locationChangeHandler(e));\n\n // adding a reference to the layout module is creating a circular dependency\n this.mediaService\n .observe()\n .pipe(\n filterSportsEmitLast(),\n map(() => ({ large: this.mediaService.isActive('gt-md') })),\n distinctUntilKeyChanged('large'),\n )\n .subscribe(this.screenSizeChanged);\n\n if (this.epcotConfigService.isEnabled()) {\n this.headerSearchService.clickEvent.pipe(filterSports()).subscribe((_) => {\n this.open('betfinder');\n });\n }\n\n this.router.events\n .pipe(\n filterSports(),\n filter((event) => event instanceof RouterEvent),\n )\n .subscribe((event) => {\n if (!(event instanceof RouterEvent)) {\n return;\n }\n const { search } = this.urlService.parse(event.url);\n if (search.has('popup')) {\n const requestedPopup = search.get('popup') as PopupTypes;\n for (let i = this.popupStack.length - 1; i >= 0; i--) {\n if (this.popupStack[i].name === requestedPopup) {\n break;\n }\n this.close(PopupAction.CloseByNavigation);\n }\n\n return;\n }\n\n if (this.isOpen()) {\n this.closeAll();\n }\n });\n }\n\n get setLastActiveItem(): Observable {\n return this._setLastActiveItem.asObservable();\n }\n\n setFullReloadRequired(): void {\n this.fullReloadRequired = true;\n }\n\n configure(name: PopupTypes, component: any, options: ModalDialogOptions): void {\n if (this.popups[name]) {\n throw new Error(`Popup '${name}' has already been configured`);\n }\n\n this.popups[name] = {\n component,\n options,\n };\n }\n\n updateDialogAnimation(\n name: PopupTypes,\n animationType?: DialogAnimation,\n showBackNav: boolean = false,\n closePreviousModalWindow: boolean = false,\n ): void {\n const popupConfig = this.popups[name];\n if (popupConfig?.options?.settings) {\n popupConfig.options.settings.animation = animationType;\n popupConfig.options.settings.showBackNav = showBackNav;\n popupConfig.options.settings.closePrevious = closePreviousModalWindow;\n }\n }\n\n updateDialogTitle(name: PopupTypes, title: string): void {\n this.popupStack.find((p) => p.name === name)?.handle.updateTitle(title);\n }\n\n open(name: PopupTypes, animationType?: DialogAnimation, options?: ModalDialogOptions): void {\n try {\n if (this.opening) {\n return;\n }\n this.opening = true;\n if (!this.popups[name]) {\n throw new Error(`Popup '${name}' cannot be opened as it has not been configured`);\n }\n\n const popupAlreadyOpened = this.popupStack.some((ps) => ps.name === name);\n if (popupAlreadyOpened) {\n this.opening = false;\n\n return;\n }\n\n if (this.popups[name].options.settings?.needsAuthentication) {\n if (!this.userService.isAuthenticated) {\n this.handlePopupUnauth(name);\n this.opening = false;\n\n return;\n }\n }\n\n const parentPopupCloseDelay = 50;\n\n if (this.currentPopup) {\n const popupConfig = this.popups[this.currentPopup];\n const closePrevious = this.popups[name]?.options?.settings?.closePrevious;\n if (popupConfig.options.settings?.closeOnChildPopup || closePrevious) {\n this.close(PopupAction.CloseByNextPopup);\n }\n if (name === ModelPopupTypes.MyBets && animationType && popupConfig.options.settings) {\n popupConfig.options.settings.animation = animationType;\n }\n }\n this.currentPopup = name;\n\n if (options) {\n this.popups[this.currentPopup].options = {\n data: { ...this.popups[this.currentPopup].options.data, ...options.data },\n settings: { ...this.popups[this.currentPopup].options.settings, ...options.settings },\n };\n }\n\n this.timerService.setTimeout(async () => {\n await this.openHandler();\n this.opening = false;\n }, parentPopupCloseDelay);\n } catch (e) {\n this.opening = false;\n throw e;\n }\n }\n\n close(action: PopupAction = PopupAction.ManualClose, index: number = -1): void {\n const popupHandle = index >= 0 ? this.popupStack[index] : this.popupStack[this.popupStack.length - 1];\n\n if (!popupHandle) {\n return;\n }\n\n this.dispatcher.dispatch(RacingEvents.Popup.PopupClosed);\n const isBabPopup = popupHandle.name === 'bet-builder-overlay';\n\n if (isBabPopup && this.appConfig.modalDialogGesturesEnabled) {\n this.modalAnimationHelper.addSlideWithScale();\n this.timerService.setTimeoutOutsideAngular(() => {\n popupHandle.handle.close({ closeAction: action });\n }, 350);\n } else {\n popupHandle.handle.close({ closeAction: action });\n }\n\n this.openedModalsStorage.tryCloseSeeAll(isBabPopup);\n this.currentPopup = null;\n }\n\n dismissPreviousPopup(popupName: PopupTypes, action: PopupAction = PopupAction.ManualClose, windowClass: string = ''): void {\n const popupHandle = this.popupStack?.find((p) => p.name === popupName);\n\n if (!popupHandle || popupHandle.name !== popupName) {\n return;\n }\n\n this.dispatcher.dispatch(RacingEvents.Popup.PopupClosed, popupName);\n if (windowClass) {\n if (this.appConfig.modalDialogGesturesEnabled) {\n this.modalAnimationHelper.addSlideWithScale();\n } else {\n popupHandle.handle.addWindowClass(windowClass);\n }\n\n this.timerService.setTimeoutOutsideAngular(\n () => {\n popupHandle.handle.dismiss({ closeAction: action });\n },\n this.appConfig.modalDialogGesturesEnabled ? 350 : 500,\n );\n } else {\n popupHandle.handle.dismiss({ closeAction: action });\n }\n\n this.currentPopup = null;\n }\n\n closeAll(): void {\n for (let index = this.popupStack.length - 1; index >= 0; index--) {\n this.close(PopupAction.ManualClose, index);\n }\n\n this.popupStack = [];\n }\n\n toggle(name: PopupTypes): void {\n if (this.isOpen()) {\n this.close();\n } else {\n this.open(name);\n }\n }\n\n isOpen(): boolean {\n return !!this.popupStack.length;\n }\n\n openBetslip(fromBottomNav?: boolean): void {\n if (fromBottomNav) {\n this.tracker.track(trackingConstants.EVENT_PAGE_VIEW, {\n [trackingConstants.PAGE_NAME]: 'M2_slip_open_bottomBubble',\n [trackingConstants.PAGE_URL]: 'in11288',\n });\n } else {\n this.tracker.track(trackingConstants.EVENT_PAGE_VIEW, {\n [trackingConstants.PAGE_NAME]: 'M2_slip_open_topRightButton',\n [trackingConstants.PAGE_URL]: 'in11246',\n });\n }\n\n this.open(ModelPopupTypes.Betslip);\n }\n\n isPopupOpen(popupName: string): boolean {\n return popupName === this.currentPopup;\n }\n\n isPopupInStack(popupName: string): boolean {\n return this.popupStack.some((popup) => popup.name === popupName);\n }\n\n private screenSizeChanged = (screen: { large: boolean }): void => {\n this.largeScreen = screen.large;\n if (screen.large) {\n if (this.currentPopup === ModelPopupTypes.Betslip) {\n this.close(PopupAction.ManualClose);\n }\n }\n };\n\n private async openInternal(name: PopupTypes | null): Promise {\n if (!name) {\n throw new Error(`Trying to open popup with no name`);\n }\n\n if (!this.popups[name]) {\n throw new Error(`Popup '${name}' cannot be opened as it has not been configured`);\n }\n\n this.dispatcher.dispatch(RacingEvents.Popup.BeforePopupDisplayed, name);\n\n const popupConfig = this.popups[name];\n popupConfig.options.settings = extend({}, popupConfig.options.settings, {\n beforeDismiss: () => {\n this.dispatcher.dispatch(RacingEvents.Popup.PopupClosed, name);\n\n return true;\n },\n });\n if (this.currentPopup === 'sports') {\n popupConfig.options.settings.container = this._doc.querySelector('.az-menu-container') || undefined;\n }\n const component = isFunction(popupConfig.component) ? await popupConfig.component() : popupConfig.component;\n const handle = this.dialogService.openPopup(component, popupConfig.options);\n let currentUrl = this.navigationService.location.path();\n if (currentUrl === this.urlHelperService.getAtoZUrl()) {\n currentUrl = this.urlHelperService.getHomeUrl();\n }\n const currentQueryParams = this.navigationService.location.absUrl().split('?')[1];\n const queryParams = `popup=${name}`;\n if (currentQueryParams !== queryParams) {\n this.location.go(currentUrl, queryParams);\n }\n this.popupStack.push({ name, handle });\n this.show();\n\n handle.result.then((r) => this.onClosing(r, name)).catch((e) => this.onClosing(e, name));\n }\n\n private locationChangeHandler(e: LocationChangeEvent): void {\n if (e.nextUrl) {\n const nextUrl = this.urlService.parse(e.nextUrl);\n if (nextUrl.search.has('popup')) {\n const requestedPopup = nextUrl.search.get('popup') as PopupTypes;\n if (this.shouldSkipLocationChange(requestedPopup)) {\n return;\n }\n const popupInStack = this.popupStack.some((ps) => ps.name === requestedPopup);\n\n if (!popupInStack && (!this.largeScreen || this.allowedInLargeScreen(requestedPopup))) {\n this.open(requestedPopup);\n }\n\n return;\n }\n }\n this.closeAll();\n\n const previousUrl = this.urlService.parse(e.previousUrl);\n if (previousUrl.search.has('popup') && previousUrl.search.get('popup') === 'sports') {\n this._setLastActiveItem.next();\n }\n }\n\n private handlePopupUnauth(name: string): void {\n const returnUrl = `?popup=${name}`;\n /* \n Switching to the home URL as the reference for popup navigation. \n This is necessary because if the popup URL remains unchanged after login, the locationChangeHandler won't detect it to trigger popup opening. \n */\n this.navigationService.goTo(this.urlHelperService.getHomeUrl(), { replace: true });\n\n // On successful login url locationchanger can handle the popup as the url changes\n this.menuActionsService.invoke(MenuAction.GOTO_LOGIN, '', [undefined, undefined, { returnUrl }]);\n }\n\n private shouldSkipLocationChange(popup: PopupTypes): boolean {\n return ['sports', 'bet-builder-overlay', 'event-switcher'].indexOf(popup) > -1;\n }\n\n private allowedInLargeScreen(popup: PopupTypes): boolean {\n return (\n [\n 'betfinder',\n 'sports',\n 'grouped-sports',\n 'bet-radar',\n 'archived-bets',\n 'bet-builder-overlay',\n 'hr-help-overlay',\n 'gr-help-overlay',\n 'ngrx-reports',\n ].indexOf(popup) > -1\n );\n }\n\n private openHandler = async (): Promise => {\n const oldPopup = this.popupStack.length > 0 ? this.popupStack[this.popupStack.length - 1].name : '';\n await this.openInternal(this.currentPopup);\n this.sensitivePageTracker.dispatchSensitivePagesEvent(this.currentPopup, oldPopup);\n };\n\n private onClosing = (result: PopupClosingResult | ModalDismissReasons, name: PopupTypes) => {\n const previousPopUp = this.popupStack[0];\n this.popupStack = this.popupStack.filter((p) => p.name !== name);\n\n this.dispatcher.dispatch('ON_POPUP_CLOSED', name);\n\n if ((result as PopupClosingResult).closeAction === PopupAction.CloseByPan) {\n this.navigateOnClose();\n }\n\n if (!this.popupStack.length) {\n // last popup was closed\n this.hide();\n }\n\n if (!this.isClosedManually(result)) {\n // navigation occurred so we do nothing\n return;\n }\n\n if (this.isFirstLoadAndClosingBetslipPopup(previousPopUp.name)) {\n const oldPopup = this.popupStack.length > 0 ? this.popupStack[this.popupStack.length - 1].name : null;\n\n this.router.navigate([this.navigationService.location.path()], {\n queryParams: { popup: oldPopup },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n\n return;\n }\n\n if (!(previousPopUp.name === 'betfinder' && this.currentPopup === 'betslip')) {\n if (previousPopUp.name === 'my-bets' && this.epcotConfigService.isShowMyBetsPopup && this.#window.history.length <= 2) {\n this.redirectHelper.goHome(undefined, true);\n } else {\n this.navigateOnClose();\n }\n }\n };\n\n private navigateOnClose(): void {\n if (this.eventDetailsConfig.showInOverlay && this.currentPopup === 'event-switcher') {\n this.timerService.setTimeoutOutsideAngular(() => {\n const url = this.routerEventsService.urlHistory?.[this.routerEventsService.urlHistory.length - 1];\n this.location.replaceState(url);\n });\n } else {\n this.location.back();\n }\n }\n\n private isFirstLoadAndClosingBetslipPopup(closingPopup: string): boolean {\n return !this.navigationHelper.allowBack() && closingPopup === 'betslip';\n }\n\n private isClosedManually(result: PopupClosingResult | ModalDismissReasons): boolean {\n if (this.isPopupActionResult(result)) {\n if (result.closeAction === PopupAction.ManualClose) {\n return true;\n }\n } else {\n if (result === ModalDismissReasons.BackdropClick || result === ModalDismissReasons.Esc) {\n return true;\n }\n }\n\n return false;\n }\n\n private hide(): void {\n this.htmlNode.setCssClass('popup-on', false);\n }\n\n private show(): void {\n if (this.currentPopup === 'sports') {\n this.htmlNode.setCssClass('popup-on az-menu-desktop', true);\n\n return;\n }\n this.htmlNode.setCssClass('popup-on', true);\n }\n\n private isPopupActionResult(result: any): result is PopupClosingResult {\n return !!result?.closeAction;\n }\n\n readonly #window = inject(WINDOW);\n}\n","import { OfferSource } from '@cds';\nimport {\n FixtureStage,\n FixtureType,\n FixtureViewType,\n Parameter,\n ParticipantMarketType,\n ParticipantStatus,\n ParticipantType,\n TotalsPrefix,\n} from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { MarketSubType, MarketType } from '@frontend/sports/betting-offer/feature/types';\nimport { Odds } from '@frontend/sports/odds/feature';\nimport { SourceOfPick } from 'packages/sports/web/app/src/betslip-base/models/pick-models';\nimport { NumpadAction } from 'packages/sports/web/app/src/numpad/model';\n\nimport { BetslipGroupInformation } from '../groups/betslip-group-information';\nimport { CombinationPrevention } from './combo-prevention';\nimport { GroupLegState } from './group-leg-state';\nimport { MinimumCombination } from './minimum-combination';\nimport { SignedName } from './signed-name.model';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n} from './sport-specific/betslip-v2-horse-race-picks';\n\nexport enum PickType {\n V1Pick,\n V2ParticipantPick,\n V2OptionMarketPick,\n BetBuilderPick,\n GroupedPicks,\n V2OptionMarketXcastPick,\n}\n\nexport enum PickSubType {\n None /* Dummy value for base classes. */,\n V1Pick,\n HorseWinPick /* V2ParticipantPick */,\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HorseXCastPick /* V2ParticipantPick */,\n HorseOptionMarketPick /* V2 Horse Option Market Pick */,\n GolfWinPick /* V2ParticipantPick */,\n BetBuilderPick,\n StandardV2Pick /* V2 Option Market (Soccer, ... ) */,\n UnknownPick,\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HorseOptionMarketXCastRacePick /* V2 Horse Option Market XCastPick*/,\n GroupedPicks,\n}\n\nexport enum ParticipantPickType {\n Win = 1,\n Forecast = 2,\n CombinationForecast = 3,\n Tricast = 4,\n CombinationTricast = 5,\n Freeform = 6,\n Derived = 7,\n None = 8,\n}\n\nexport enum HorseRacingPreFix {\n Win = 'win',\n Forecast = 'fc',\n CombinationForecast = 'cfc',\n Tricast = 'tc',\n CombinationTricast = 'ctc',\n}\n\nexport enum ResultStatus {\n Open = 1,\n Lost = 2,\n Won = 3,\n Canceled = 5,\n Unknown = 0,\n}\n\nexport enum PriceType {\n Fixed = 1,\n StartingPrice = 2,\n NoPrice = 3,\n}\n\nexport enum QuickBetPickType {\n SinglePick,\n ComboPicks,\n BetBuilderPicks,\n BetBuilderPlusPicks,\n}\n\nexport interface IBasePrice {\n type: PriceType;\n nativeOdds: Odds;\n}\n\nexport interface IPrice extends IBasePrice {\n id: number;\n isVisible: boolean;\n marketId: number;\n}\n\nexport enum PickBettingState {\n // Pick open for betting\n Open,\n // Currently locked\n Locked,\n // Pick closed\n Closed,\n}\n\nexport interface IBetslipPickStorage {\n id: string;\n priceHistory: IPrice[];\n priceType: PriceType;\n isNewCustomerOffer: boolean;\n acceptedPrice: {\n price: IPrice | null;\n isManuallyAccepted: boolean;\n };\n market: IBetslipPickMarket | null;\n pickSubType: PickSubType;\n subscriptionContext: string[];\n tracking: IPickTracking;\n boostedPriceDetails: IBoostedPrice | undefined;\n liveAlert?: boolean;\n groupInfo?: BetslipGroupInformation;\n parentLinkedEventId?: string;\n totalsPrefix?: TotalsPrefix;\n fromHybridFixture?: boolean;\n}\n\nexport enum PickOddsState {\n Open,\n Locked,\n Closed,\n}\n\nexport interface IAcceptedPrice {\n price: IPrice | null; // Accepted price will be null when current price is undefined.\n /**\n * When price is accepted automatically from user odd acceptance isManuallyAccepted is false.\n * When price is accepted manually from user when clicks on the button is true\n */\n isManuallyAccepted: boolean;\n}\n\nexport interface IBoostedPrice {\n isPriceBoosted?: boolean;\n boostedPrice: IPrice | undefined;\n boostedPriceId?: number;\n}\n\nexport interface IPickTracking {\n //below was added to support dynamic keys that are added to pick tracking without being defined here, this is a hack, and should never be reused or copied\n [key: string]: string | boolean | NumpadAction | undefined | { [key: string]: string };\n source: string;\n sourceOfBetSuffix?: string;\n affiliate?: boolean;\n numpadAction?: NumpadAction;\n additional?: { [key: string]: string };\n sourceOfPick?: SourceOfPick;\n betDetails?: string;\n betCalender?: string;\n controllerTabName?: string;\n playerName?: string;\n betType?: string;\n gameDetails?: string;\n moduleType?: string;\n contentPosition?: string;\n variant?: string;\n sheetviewSuffix?: string;\n parentLeagueId?: number;\n betPickType?: string;\n}\n\nexport interface IBetslipPickMarket {\n id: number;\n isVisible: boolean;\n gridGroups?: string[];\n marketType?: MarketType | ParticipantMarketType;\n marketSubType?: MarketSubType;\n isBetBuilderEnabled: boolean;\n templateId?: number;\n}\n\nexport interface BetslipV1PickSport {\n id: number;\n name: SignedName;\n}\n\nexport interface BetslipV1PickTeaserData {\n crossSportIds: number[];\n crossLeagueIds: number[];\n}\n\nexport interface BetslipV1PickLeague {\n id: number;\n name: SignedName;\n parentLeagueId: number | null;\n}\n\nexport interface BetslipV1PickRegion {\n id: number;\n name: SignedName;\n}\n\nexport class BetslipV1PickEvent {\n id: string;\n name: SignedName;\n groupId: number | null;\n eventDate: Date;\n cutOffDate: Date;\n isLive: boolean;\n isPublished: boolean;\n participants?: EventParticipant[];\n viewType?: FixtureViewType;\n}\n\nexport interface IBetslipV1PickEventStorage {\n id: string;\n name: SignedName;\n groupId: number | null;\n eventDate: string;\n cutOffDate: string;\n isLive: boolean;\n isPublished: boolean;\n participants?: EventParticipant[];\n viewType?: FixtureViewType;\n}\n\nexport interface BetslipV1PickMarket extends IBetslipPickMarket {\n name: SignedName;\n templateId: number;\n comboPrevention: CombinationPrevention;\n minimumCombo: MinimumCombination;\n isClosed: boolean;\n templateCategoryId?: number;\n}\n\nexport interface BetslipV1PickOption {\n id: number;\n name: SignedName;\n price: IPrice;\n}\n\nexport interface IBetslipV1PickDto {\n sport: BetslipV1PickSport;\n league: BetslipV1PickLeague;\n region: BetslipV1PickRegion;\n event: BetslipV1PickEvent;\n market: BetslipV1PickMarket;\n option: BetslipV1PickOption;\n}\n\nexport interface IBetslipV1PickStorage extends IBetslipPickStorage {\n sport: BetslipV1PickSport;\n league: BetslipV1PickLeague;\n region: BetslipV1PickRegion;\n event: IBetslipV1PickEventStorage;\n market: BetslipV1PickMarket;\n option: BetslipV1PickOption;\n teaser: BetslipV1PickTeaserData;\n}\n\nexport interface IBetslipV2PickStorage extends IBetslipPickStorage {\n fixture: IBetslipV2PickFixtureStorage;\n}\n\nexport interface IBetslipV2PickFixture {\n fixtureId: string;\n sportId: number;\n sportName: SignedName;\n eventDate: Date;\n cutOffDate: Date;\n fixtureType: FixtureType;\n stage: FixtureStage;\n name: SignedName;\n competitionGroupId: number | null;\n virtual: boolean;\n league: {\n id: number;\n name: SignedName;\n realCompetitionId?: number;\n } | null;\n region: {\n id: number;\n name: SignedName;\n } | null;\n linkedEventIds: number[];\n participants?: EventParticipant[];\n viewType?: FixtureViewType;\n}\n\nexport interface IBetslipV2PickFixtureStorage extends Omit {\n eventDate: string;\n cutOffDate: string;\n}\n\nexport interface IBetslipV2HorseRacePickFixture extends IBetslipV2PickFixture {\n bestOddsGuarantee: boolean;\n isRaceOff: boolean;\n}\n\nexport interface IBetslipV2HorseRacePickFixtureStorage extends Omit {\n eventDate: string;\n cutOffDate: string;\n}\n\nexport interface IBetslipV2GolfPickFixture extends IBetslipV2PickFixture {\n fixtureGroup: {\n id: number;\n name: SignedName;\n };\n}\n\nexport interface IBetslipV2GolfPickFixtureStorage extends Omit {\n eventDate: string;\n cutOffDate: string;\n}\n\nexport interface IBetslipV2ParticipantPickStorage extends IBetslipV2PickStorage {\n market: IBetslipV2PickParticipantMarket;\n participants: IBetslipV2PickParticipant[];\n pickSubType: PickSubType;\n betType: string;\n betTypeAltTranslation: string;\n}\n\nexport interface IBetslipV2PickParticipantMarket extends IBetslipPickMarket {\n marketType: ParticipantMarketType;\n}\n\nexport interface IPlaceTerms {\n denominator: number;\n numerator: number;\n places: number;\n}\n\nexport interface IBetslipV2WinParticipantPickStorage extends IBetslipV2ParticipantPickStorage {\n market: IBetslipV2PickWinParticipantMarket;\n}\n\nexport interface IBetslipV2PickWinParticipantMarket extends IBetslipV2PickParticipantMarket {\n isEachWay: boolean;\n placeTerms: IPlaceTerms | null;\n isStartingPriceAvailable: boolean;\n}\n\nexport interface IBetslipV2PickParticipant {\n /**\n * Fixture participant id, participant id in the fixture\n */\n fixtureParticipantId: number;\n /**\n * Participant id in the master data. e.g. horse id number.\n */\n participantId: number;\n name: SignedName;\n type: ParticipantType;\n status: ParticipantStatus;\n position: number;\n prices: IPrice[];\n drawNumber?: number;\n}\n\nexport interface IBetslipV2OptionMarket extends IBetslipPickMarket {\n name: SignedName;\n comboPrevention: CombinationPrevention;\n minimumCombo: MinimumCombination;\n parameters?: Parameter[];\n isEachWay: boolean;\n placeTerms: IPlaceTerms | null;\n isStartingPriceAvailable?: boolean;\n isFixedPriceAvailable?: boolean;\n isClosed: boolean;\n}\n\nexport interface IBetslipV2Option {\n id: number;\n name: SignedName;\n isVisible: boolean;\n prices: IPrice[];\n boostedPrice?: IPrice;\n position?: number;\n isDraw?: boolean;\n}\n\nexport interface ISportcastOption {\n longId: string;\n name: string;\n}\n\nexport interface IBetslipV2OptionMarketPickStorage extends IBetslipV2PickStorage {\n market: IBetslipV2OptionMarket;\n isFavouritePick?: boolean;\n option: IBetslipV2Option;\n pickSubType: PickSubType;\n pickType?: ParticipantPickType;\n isFromNewCustomerOffer: boolean;\n siblingOption?: IBetslipV2Option;\n}\n\nexport interface IBetslipV2OptionMarketXCastPickStorage extends IBetslipV2PickStorage {\n market: IBetslipV2OptionMarket;\n options: IBetslipV2Option[];\n pickSubType: PickSubType;\n betType: string;\n betTypeAltTranslation: string;\n}\n\nexport interface IBetslipV2GolfPickStorage extends Omit {\n fixture: IBetslipV2GolfPickFixtureStorage;\n}\n\nexport interface IBetslipV2GolfWinParticipantPickStorage\n extends Omit,\n Omit {\n // fixture type comes from IBetslipV2GolfPickStorage\n // market type comes from IBetslipV2WinParticipantPickStorage\n}\n\nexport interface GolfPick {\n fixture: IBetslipV2GolfPickFixture;\n eventName: SignedName;\n marketName: SignedName;\n}\n\nexport interface IBetslipV2HorseRacePickStorage extends Omit {\n fixture: IBetslipV2HorseRacePickFixtureStorage;\n}\n\nexport interface IBetslipV2HorseRaceParticipantPickStorage\n extends Omit,\n Omit {\n participants: IBetslipV2PickParticipant[];\n _marketName: string;\n}\n\nexport interface IBetslipV2HorseRaceWinParticipantPickStorage\n extends Omit,\n Omit {}\n\nexport interface IBetslipV2HorseRaceOptionMarketPickStorage\n extends Omit,\n Omit {\n betType: string;\n betTypeAltTranslation: string;\n}\n\nexport interface IBetslipV2HorseRaceOptionMarketXCastPickStorage\n extends Omit,\n Omit {}\n\nexport interface HorseRacePick {\n fixture: IBetslipV2HorseRacePickFixture;\n leagueId: number;\n leagueName: SignedName;\n regionId: number;\n regionName: SignedName;\n}\n\nexport interface HorseRaceParticipantPick extends HorseRacePick {\n participants: IBetslipV2PickParticipant[];\n setMarketName(value: string, altTranslation?: string): void;\n}\n\nexport type HorseRaceParticipantPickType = BetslipV2HorseRaceWinParticipantPick | BetslipV2HorseRaceXCastPick;\nexport type HorseRacePickType = HorseRaceParticipantPickType | BetslipV2HorseRaceOptionMarketPick;\n\nexport interface IBetslipBetBuilderPickStorage extends IBetslipV2OptionMarketPickStorage {\n eventId: string;\n event: IEventData;\n sportcastOptions: ISportcastOption[];\n sportcastId?: number;\n betgeniusId?: number;\n betSlipUid?: string;\n useV2key?: boolean;\n longIds?: string[];\n}\n\nexport interface IEventData {\n league: {\n id: number;\n name: SignedName;\n parentLeagueId?: number;\n };\n event: {\n id: string;\n name: SignedName;\n date: Date;\n isLive?: boolean;\n groupId?: number;\n participants?: EventParticipant[];\n viewType?: FixtureViewType;\n };\n region: {\n id: number;\n name: SignedName;\n };\n source: OfferSource;\n betBuilderTradingV2FixtureId?: string;\n}\n\nexport interface BetslipGroupPickStorage extends IBetslipPickStorage {\n price: IPrice;\n picks: IBetslipPickStorage[];\n SGPId: string;\n legsState: Record;\n isSuspended: boolean;\n}\n\nexport enum PickUpdateType {\n None = 0,\n Unknown = 1,\n /* eslint-disable no-bitwise */\n MarketVisibility = 1 << 1,\n // eslint-disable-next-line no-bitwise\n PriceVisibility = 1 << 2,\n // eslint-disable-next-line @typescript-eslint/no-shadow\n Odds = 1 << 3,\n /* eslint-enable no-bitwise */\n}\n","import { BetBuilderProvider } from '@cds/betting-offer/add-ons';\n\nimport { BetslipGroupInformation } from '../groups/betslip-group-information';\nimport { HorseRacingPreFix, ParticipantPickType, PickType } from './pick-models';\n\nexport abstract class PickId {\n fixtureId: string;\n subscriptionContext: string[] = [];\n parentLinkedEventId?: string;\n\n protected constructor(readonly id: string) {}\n\n static isEmpty(id: string | PickId): boolean {\n if (typeof id === 'string') {\n return !id;\n } else {\n return id.isEmpty();\n }\n }\n\n static Empty(): PickId {\n class EmptyPickId extends PickId {\n constructor() {\n super('');\n this.fixtureId = '';\n }\n\n getPickType(): PickType {\n throw Error('Empty Pick Id');\n }\n\n copy(): PickId {\n return new EmptyPickId();\n }\n }\n\n return new EmptyPickId();\n }\n\n isEmpty(): boolean {\n return !this.id;\n }\n\n isEqual(other?: PickId | string): boolean {\n if (!other) {\n return false;\n }\n\n if (typeof other === 'string') {\n return this.id === other;\n }\n\n return this.id === other.id;\n }\n\n toString(): string {\n return this.id;\n }\n\n abstract getPickType(): PickType;\n\n abstract copy(): PickId;\n}\n\nexport class V1PickId extends PickId {\n private static readonly ID_PREFIX = 'v1';\n private static readonly PICK_REG_EX: RegExp = new RegExp(`${V1PickId.ID_PREFIX}-e:(\\\\d+)/m:(\\\\d+)/o:(-?\\\\d+)`);\n\n private static asString(eventId: string, marketId: number, optionId: number): string {\n return `${V1PickId.ID_PREFIX}-e:${eventId}/m:${marketId}/o:${optionId}`;\n }\n\n static isId(id: PickId): id is V1PickId;\n static isId(id: string): boolean;\n static isId(id: string | PickId): boolean {\n const asString = typeof id === 'string' ? id : id.toString();\n\n return V1PickId.PICK_REG_EX.test(asString);\n }\n\n /**\n * Do not use it. Use pickIdFactory\n *\n * @param id\n */\n static fromString(id: string): V1PickId {\n const matches = id.match(V1PickId.PICK_REG_EX) as RegExpMatchArray;\n\n return new V1PickId(matches[1] /*eventId*/, +matches[2] /*marketId*/, +matches[3] /*optionId*/);\n }\n\n constructor(\n public eventId: string,\n public marketId: number,\n public optionId: number,\n ) {\n super(V1PickId.asString(eventId, marketId, optionId));\n this.fixtureId = eventId.toString();\n }\n\n getPickType(): PickType {\n return PickType.V1Pick;\n }\n\n copy(): V1PickId {\n return new V1PickId(this.eventId, this.marketId, this.optionId);\n }\n}\n\nexport abstract class V2PickId extends PickId {\n static isId(id: PickId): id is V2PickId {\n return id instanceof V2PickId;\n }\n\n protected constructor(\n id: string,\n public override fixtureId: string,\n ) {\n super(id);\n }\n}\n\nexport class V2ParticipantPickId extends V2PickId {\n protected static readonly ID_PREFIX = 'v2-p';\n private static readonly PICK_REG_EX: RegExp = new RegExp(\n `${V2ParticipantPickId.ID_PREFIX}-(win|fc|cfc|tc|ctc)-f:([\\\\d:]+)/m:(\\\\d+)((/p:(\\\\d+):(\\\\d+))+)`,\n );\n\n private static readonly PICK_PARTICIPANT_REG_EX: RegExp = new RegExp('/p:(\\\\d+):(\\\\d+)');\n\n private static getPrefix(pickType: ParticipantPickType): string {\n switch (pickType) {\n case ParticipantPickType.Win:\n return 'win';\n case ParticipantPickType.Forecast:\n return 'fc';\n case ParticipantPickType.CombinationForecast:\n return 'cfc';\n case ParticipantPickType.Tricast:\n return 'tc';\n case ParticipantPickType.CombinationTricast:\n return 'ctc';\n }\n\n return '';\n }\n\n private static asString(fixtureId: string, marketId: number, participantIds: { [id: number]: number }, pickType: ParticipantPickType): string {\n const prefix = `${V2ParticipantPickId.ID_PREFIX}-${V2ParticipantPickId.getPrefix(pickType)}-f:${fixtureId}/m:${marketId}/`;\n const entries = Object.entries(participantIds).sort((a, b) => +a[0] - +b[0]);\n const participants: string[] = [];\n for (const [key, value] of entries) {\n participants.push(`p:${key}:${value}`);\n }\n\n return prefix + participants.join('/');\n }\n\n static override isId(id: PickId): id is V2ParticipantPickId;\n static override isId(id: string): boolean;\n static override isId(id: string | PickId): boolean {\n const asString = typeof id === 'string' ? id : id.toString();\n\n return V2ParticipantPickId.PICK_REG_EX.test(asString);\n }\n\n /**\n * Do not use it. Use pickIdFactory\n *\n * @param id\n */\n static fromString(id: string): V2ParticipantPickId {\n const matches = id.match(V2ParticipantPickId.PICK_REG_EX) as RegExpMatchArray;\n const type = matches[1];\n let pickType: ParticipantPickType;\n switch (type) {\n case 'win':\n pickType = ParticipantPickType.Win;\n break;\n case 'fc':\n pickType = ParticipantPickType.Forecast;\n break;\n case 'cfc':\n pickType = ParticipantPickType.CombinationForecast;\n break;\n case 'tc':\n pickType = ParticipantPickType.Tricast;\n break;\n case 'ctc':\n pickType = ParticipantPickType.CombinationTricast;\n break;\n default:\n throw new Error('Unknown participant pick type');\n }\n const fixtureId = matches[2];\n const marketId = +matches[3];\n const participantIds: { [id: number]: number } = {};\n let match = matches[4];\n while (match.length > 0) {\n const pm = match.match(V2ParticipantPickId.PICK_PARTICIPANT_REG_EX) as RegExpMatchArray;\n participantIds[+pm[1]] = +pm[2];\n match = match.replace(V2ParticipantPickId.PICK_PARTICIPANT_REG_EX, '');\n }\n\n return pickType === ParticipantPickType.Win\n ? new V2WinPickId(fixtureId, marketId, +Object.keys(participantIds)[0])\n : new V2ParticipantPickId(fixtureId, marketId, participantIds, pickType);\n }\n\n constructor(\n fixtureId: string,\n public marketId: number,\n public participantIds: { [id: number]: number },\n public pickType: ParticipantPickType,\n ) {\n super(V2ParticipantPickId.asString(fixtureId, marketId, participantIds, pickType), fixtureId);\n }\n\n getPickType(): PickType {\n return PickType.V2ParticipantPick;\n }\n\n copy(): V2ParticipantPickId {\n return new V2ParticipantPickId(this.fixtureId, this.marketId, this.participantIds, this.pickType);\n }\n\n get participants(): number[] {\n return Object.keys(this.participantIds).map((k) => +k);\n }\n}\n\nexport class V2WinPickId extends V2ParticipantPickId {\n private static readonly V2WIN_PICK_REG_EX: RegExp = new RegExp(`${V2ParticipantPickId.ID_PREFIX}-win-f:([\\\\d:]+)/m:(\\\\d+)((/p:(\\\\d+):(\\\\d+))+)`);\n\n constructor(\n public override fixtureId: string,\n public override marketId: number,\n public participantId: number,\n ) {\n super(fixtureId, marketId, { [participantId]: 1 }, ParticipantPickType.Win);\n }\n\n static override isId(id: PickId): id is V2WinPickId;\n static override isId(id: string): boolean;\n static override isId(id: string | PickId): boolean {\n const asString = typeof id === 'string' ? id : id.toString();\n\n return V2WinPickId.V2WIN_PICK_REG_EX.test(asString);\n }\n\n override copy(): V2WinPickId {\n return new V2WinPickId(this.fixtureId, this.marketId, this.participantId);\n }\n}\nexport class V2OptionMarketPickId extends V2PickId {\n private static readonly ID_PREFIX = 'v2-om';\n private static readonly PICK_REG_EX: RegExp = new RegExp(`${V2OptionMarketPickId.ID_PREFIX}-f:([\\\\d:]+)/om:(\\\\d+)/o:(\\\\d+)/p:(\\\\d+)`);\n\n static asString(fixtureId: string, optionMarketId: number, optionId: number, priceId: number): string {\n return `${V2OptionMarketPickId.ID_PREFIX}-f:${fixtureId}/om:${optionMarketId}/o:${optionId}/p:${priceId}`;\n }\n\n /**\n * Do not use it. Use pickIdFactory\n *\n * @param id\n */\n static fromString(id: string): V2OptionMarketPickId {\n const matches = id.match(V2OptionMarketPickId.PICK_REG_EX) as RegExpMatchArray;\n\n return new V2OptionMarketPickId(matches[1] /*fixtureId*/, +matches[2] /*marketId*/, +matches[3] /*optionId*/, +matches[4] /*priceId*/);\n }\n\n constructor(\n public override fixtureId: string,\n public optionMarketId: number,\n public optionId: number,\n public priceId: number,\n ) {\n super(V2OptionMarketPickId.asString(fixtureId, optionMarketId, optionId, priceId), fixtureId);\n }\n\n static override isId(id: PickId): id is V2OptionMarketPickId;\n static override isId(id: string): boolean;\n static override isId(id: string | PickId): boolean {\n const asString = typeof id === 'string' ? id : id.toString();\n\n return V2OptionMarketPickId.PICK_REG_EX.test(asString);\n }\n\n copy(): V2OptionMarketPickId {\n return new V2OptionMarketPickId(this.fixtureId, this.optionMarketId, this.optionId, this.priceId);\n }\n\n getPickType(): PickType {\n return PickType.V2OptionMarketPick;\n }\n}\n\nexport class V2OptionMarketXCastPickId extends V2PickId {\n private static readonly ID_PREFIX = 'v2-om';\n private static readonly PICK_REG_EX: RegExp = new RegExp(\n `${V2OptionMarketXCastPickId.ID_PREFIX}-?(fc|cfc|tc|ctc)?-f:([\\\\d:]+)/om:(\\\\d+)((/o:(\\\\d+):?(\\\\d+)?)*)((/p:(\\\\d+):?(\\\\d+)?)*)`,\n );\n\n private static readonly PICK_OPTION_REG_EX: RegExp = new RegExp('/o:(\\\\d+):(\\\\d+)');\n private static readonly PICK_PRICE_REG_EX: RegExp = new RegExp('/p:(\\\\d+):(\\\\d+)');\n\n private static asString(\n fixtureId: string,\n optionMarketId: number,\n optionIds: { [id: number]: number },\n priceIds: { [id: number]: number },\n pickType: ParticipantPickType,\n ): string {\n const prefix = `${V2OptionMarketXCastPickId.ID_PREFIX}-${V2OptionMarketXCastPickId.getPrefix(pickType)}-f:${fixtureId}/om:${optionMarketId}/`;\n const optionEntries = Object.entries(optionIds).sort((a, b) => +a[0] - +b[0]);\n const options: string[] = [];\n for (const [key, value] of optionEntries) {\n options.push(`o:${key}:${value}`);\n }\n const priceEntries = Object.entries(priceIds).sort((a, b) => +a[0] - +b[0]);\n const prices: string[] = [];\n for (const [key, value] of priceEntries) {\n prices.push(`p:${key}:${value}`);\n }\n\n return prefix + options.join('/') + '/' + prices.join('/');\n }\n\n /**\n * Do not use it. Use pickIdFactory\n *\n * @param id\n */\n static fromString(id: string): V2OptionMarketXCastPickId {\n const matches = id.match(V2OptionMarketXCastPickId.PICK_REG_EX) as RegExpMatchArray;\n const type = matches[1];\n let pickType: ParticipantPickType;\n switch (type) {\n case HorseRacingPreFix.Forecast:\n pickType = ParticipantPickType.Forecast;\n break;\n case HorseRacingPreFix.CombinationForecast:\n pickType = ParticipantPickType.CombinationForecast;\n break;\n case HorseRacingPreFix.Tricast:\n pickType = ParticipantPickType.Tricast;\n break;\n case HorseRacingPreFix.CombinationTricast:\n pickType = ParticipantPickType.CombinationTricast;\n break;\n default:\n throw new Error('Unknown option market pick type');\n }\n const fixtureId = matches[2];\n const marketId = +matches[3];\n const optionIds: { [id: number]: number } = {};\n const prices: { [id: number]: number } = {};\n let match = matches[4];\n while (match.length > 0) {\n const pm = match.match(V2OptionMarketXCastPickId.PICK_OPTION_REG_EX) as RegExpMatchArray;\n optionIds[+pm[1]] = +pm[2];\n match = match.replace(V2OptionMarketXCastPickId.PICK_OPTION_REG_EX, '');\n }\n let pricematch = matches[8];\n while (pricematch.length > 0) {\n const pm = pricematch.match(V2OptionMarketXCastPickId.PICK_PRICE_REG_EX) as RegExpMatchArray;\n prices[+pm[1]] = +pm[2];\n pricematch = pricematch.replace(V2OptionMarketXCastPickId.PICK_PRICE_REG_EX, '');\n }\n\n return new V2OptionMarketXCastPickId(fixtureId, marketId, optionIds, prices, pickType);\n }\n\n constructor(\n public override fixtureId: string,\n public optionMarketId: number,\n public optionIds: { [id: number]: number },\n public priceIds: { [id: number]: number },\n public pickType: ParticipantPickType,\n ) {\n super(V2OptionMarketXCastPickId.asString(fixtureId, optionMarketId, optionIds, priceIds, pickType), fixtureId);\n }\n\n static override isId(id: PickId): id is V2OptionMarketXCastPickId;\n static override isId(id: string): boolean;\n static override isId(id: string | PickId): boolean {\n const asString = typeof id === 'string' ? id : id.toString();\n\n return V2OptionMarketXCastPickId.PICK_REG_EX.test(asString);\n }\n\n copy(): V2OptionMarketXCastPickId {\n return new V2OptionMarketXCastPickId(this.fixtureId, this.optionMarketId, this.optionIds, this.priceIds, this.pickType);\n }\n\n getPickType(): PickType {\n return PickType.V2OptionMarketXcastPick;\n }\n\n private static getPrefix(pickType: ParticipantPickType): string {\n switch (pickType) {\n case ParticipantPickType.Forecast:\n return 'fc';\n case ParticipantPickType.CombinationForecast:\n return 'cfc';\n case ParticipantPickType.Tricast:\n return 'tc';\n case ParticipantPickType.CombinationTricast:\n return 'ctc';\n }\n\n return '';\n }\n}\n\nexport class BetBuilderPickId extends V2PickId {\n private static readonly ID_PREFIX = 'bb';\n private static readonly PICK_REG_EX: RegExp = new RegExp(`${BetBuilderPickId.ID_PREFIX}-e:([\\\\d:]+)/f:([\\\\d:]+)/om:(\\\\d+)/o:(\\\\d+)/p:(\\\\d+)`);\n\n private static asString(eventId: string, fixtureId: string, optionMarketId: number, optionId: number, priceId: number): string {\n const event = eventId.indexOf(':') !== -1 ? eventId.split(':').pop() : eventId;\n\n return `${BetBuilderPickId.ID_PREFIX}-e:${event}/f:${fixtureId}/om:${optionMarketId}/o:${optionId}/p:${priceId}`;\n }\n\n static override isId(id: PickId): id is BetBuilderPickId;\n static override isId(id: string): boolean;\n static override isId(id: string | PickId): boolean {\n const asString = typeof id === 'string' ? id : id.toString();\n\n return BetBuilderPickId.PICK_REG_EX.test(asString);\n }\n\n /**\n * Do not use it. Use pickIdFactory\n *\n * @param id\n */\n static fromString(id: string): BetBuilderPickId {\n const matches = id.match(BetBuilderPickId.PICK_REG_EX) as RegExpMatchArray;\n\n return new BetBuilderPickId(\n matches[1] /*eventId*/,\n matches[2] /*fixtureId*/,\n +matches[3] /*marketId*/,\n +matches[4] /*optionId*/,\n +matches[5] /*priceId*/,\n );\n }\n\n constructor(\n public eventId: string,\n fixtureId: string,\n public optionMarketId: number,\n public optionId: number,\n public priceId: number,\n ) {\n super(BetBuilderPickId.asString(eventId, fixtureId, optionMarketId, optionId, priceId), fixtureId);\n }\n\n getPickType(): PickType {\n return PickType.BetBuilderPick;\n }\n\n copy(): BetBuilderPickId {\n return new BetBuilderPickId(this.eventId, this.fixtureId, this.optionMarketId, this.optionId, this.priceId);\n }\n}\n\nexport class GroupPickId extends PickId {\n private static readonly ID_PREFIX = 'GROUP';\n private static readonly PICK_REG_EX: RegExp = new RegExp(`${GroupPickId.ID_PREFIX}-e:([^/]+)(?:\\/(.+))?`);\n\n private static asString(groupInfo: BetslipGroupInformation): string {\n return `${GroupPickId.ID_PREFIX}-e:${groupInfo.groupId}/${groupInfo.provider}`;\n }\n\n static isId(id: PickId): id is GroupPickId;\n static isId(id: string): boolean;\n static isId(id: string | PickId): boolean {\n const asString = typeof id === 'string' ? id : id.toString();\n\n return GroupPickId.PICK_REG_EX.test(asString);\n }\n\n /**\n * Do not use it. Use pickIdFactory\n *\n * @param id\n */\n static fromString(id: string): GroupPickId {\n const matches = id.match(GroupPickId.PICK_REG_EX) as RegExpMatchArray;\n\n return new GroupPickId({ groupId: matches[1], provider: matches[2] ? (matches[2] as BetBuilderProvider) : BetBuilderProvider.Angstrom });\n }\n\n constructor(public groupInfo: BetslipGroupInformation) {\n super(GroupPickId.asString(groupInfo));\n this.fixtureId = groupInfo.groupId;\n }\n\n getPickType(): PickType {\n return PickType.GroupedPicks;\n }\n\n copy(): GroupPickId {\n return new GroupPickId(this.groupInfo);\n }\n}\n\nexport type GroupEnabledPickIds = V1PickId | V2OptionMarketPickId | V2ParticipantPickId;\nexport const isGroupEnabledPickId = (pickId: PickId): pickId is GroupEnabledPickIds =>\n V1PickId.isId(pickId) || V2OptionMarketPickId.isId(pickId) || V2ParticipantPickId.isId(pickId);\n\nexport const pickIdFactory = function (id: string): PickId {\n if (V1PickId.isId(id)) {\n // V1 pick\n return V1PickId.fromString(id);\n }\n if (V2ParticipantPickId.isId(id)) {\n // V2 participant pick\n return V2ParticipantPickId.fromString(id);\n }\n if (V2OptionMarketPickId.isId(id)) {\n // V2 Option market pick\n return V2OptionMarketPickId.fromString(id);\n }\n if (V2OptionMarketXCastPickId.isId(id)) {\n return V2OptionMarketXCastPickId.fromString(id);\n }\n if (BetBuilderPickId.isId(id)) {\n return BetBuilderPickId.fromString(id);\n }\n if (PickId.isEmpty(id)) {\n return PickId.Empty();\n }\n if (GroupPickId.isId(id)) {\n return GroupPickId.fromString(id);\n }\n throw new Error('Unknown pick id string');\n};\n\nexport const pickIdOrNullFactory = function (id: string): PickId | null {\n if (V1PickId.isId(id)) {\n // V1 pick\n return V1PickId.fromString(id);\n }\n if (V2ParticipantPickId.isId(id)) {\n // V2 participant pick\n return V2ParticipantPickId.fromString(id);\n }\n if (V2OptionMarketPickId.isId(id)) {\n // V2 Option market pick\n return V2OptionMarketPickId.fromString(id);\n }\n if (V2OptionMarketXCastPickId.isId(id)) {\n return V2OptionMarketXCastPickId.fromString(id);\n }\n if (BetBuilderPickId.isId(id)) {\n return BetBuilderPickId.fromString(id);\n }\n if (PickId.isEmpty(id)) {\n return PickId.Empty();\n }\n if (GroupPickId.isId(id)) {\n return GroupPickId.fromString(id);\n }\n\n return null;\n};\n","export enum BetslipState {\n // ADDING/REMOVING picks\n Edit = 'Edit',\n // After place bet button, waiting for result from server\n Placing = 'Placing',\n // Place bet result display state\n Result = 'Result',\n}\n","import { BetslipDisplayMode } from '@frontend/sports/types/components/bet-placement';\nimport { IUiRootState } from 'packages/sports/web/app/src/ui-manager/ui-manager.state';\n\nimport { BetslipState } from '../../core/betslip-state';\nimport { IBetslipSourcePageState } from '../../model/edit-mybet/edit-bet-init';\nimport { IAffiliateState } from '../../modules/affiliates/state/affiliate.state';\nimport { IBetslipPlaceBetState, IBetslipPlaceButtonState } from '../../modules/betplacement/state';\nimport { IHiddenMarketState } from '../../modules/hidden-market/hidden-market.state';\nimport { IInfoMessagesState } from '../../modules/info-messages//state';\nimport { ILineSwitcherState } from '../../modules/line-switcher/state';\nimport { IBetslipNotificationState } from '../../modules/notifications/state';\nimport { OddsPreferencePopupState } from '../../modules/odds-preference-popup/odds-preference-state.model';\nimport { IBetslipOverAskState } from '../../modules/over-ask/state';\nimport { IBetslipPickState } from '../../modules/picks/state';\nimport { IQuickBetState } from '../../modules/quick-bet/quick-bet.state';\nimport { IQuickDepositState } from '../../modules/quick-deposit/quick-deposit.state';\nimport { IBetslipResultState } from '../../modules/result/state';\nimport { IRewardTokensState } from '../../modules/reward-tokens/state';\nimport { ISettingsState } from '../../modules/settings/state';\nimport { ITrackingState } from '../../modules/tracking/state';\nimport { IBetslipTypeState } from '../../modules/types/state';\nimport { IBetslipErrorsState } from '../../modules/validation/state';\n\nexport interface IBetslipState {\n base: IBetslipBaseState;\n picks: IBetslipPickState;\n types: IBetslipTypeState;\n errors: IBetslipErrorsState;\n rewardTokens: IRewardTokensState;\n placeBet: IBetslipPlaceBetState;\n result: IBetslipResultState;\n settings: ISettingsState;\n placeButton: IBetslipPlaceButtonState;\n overAskState: IBetslipOverAskState;\n lineSwitcher: ILineSwitcherState;\n notifications: IBetslipNotificationState;\n affiliateState: IAffiliateState;\n quickDeposit: IQuickDepositState;\n quickBet: IQuickBetState;\n oddsPreferencePopupState: OddsPreferencePopupState;\n hiddenMarket: IHiddenMarketState;\n sourcePageState: IBetslipSourcePageState;\n infoMessages: IInfoMessagesState;\n tracking: ITrackingState;\n}\n\nexport interface IBetslipBaseState {\n state: BetslipState;\n isBetslipKeypadOpened: boolean;\n displayMode: BetslipDisplayMode;\n linearModeCheckboxesEnabled: boolean;\n isSportcastAsComboEnabled: boolean;\n}\n\nexport const defaultBetslipBaseState: IBetslipBaseState = {\n state: BetslipState.Edit,\n isBetslipKeypadOpened: false,\n displayMode: BetslipDisplayMode.Tabbed,\n linearModeCheckboxesEnabled: true,\n isSportcastAsComboEnabled: false,\n};\n\nexport const betslipFeatureKey = 'betslip';\n\nexport interface IBetslipRootState extends IUiRootState {\n betslip: IBetslipState;\n}\n","import { BetslipDisplayMode } from '@frontend/sports/types/components/bet-placement';\nimport { createFeatureSelector, createSelector } from '@ngrx/store';\n\nimport { BetslipState } from '../../core/betslip-state';\nimport { IBetslipState, betslipFeatureKey } from './state';\n\nexport const betslipSelector = createFeatureSelector(betslipFeatureKey);\n\nexport const betslipBaseSelector = createSelector(betslipSelector, (b) => b.base);\nexport const betslipBaseStateSelector = createSelector(betslipSelector, (b) => b.base.state);\nexport const betslipDisplayModeSelector = createSelector(betslipSelector, (b) => b.base.displayMode);\nexport const selectIsLinearBetslip = createSelector(betslipDisplayModeSelector, (b) => b === BetslipDisplayMode.Linear);\nexport const selectIsLinearCheckboxesEnabled = createSelector(betslipSelector, (b) => b.base.linearModeCheckboxesEnabled);\n\nexport const selectIsPlacingBet = createSelector(betslipSelector, (b) => b.base.state === BetslipState.Placing);\n\nexport const betslipStageSelector = createSelector(betslipBaseSelector, (baseState) => baseState.state);\n","import { Decimal } from 'decimal.js';\n\nexport class Fraction {\n private static numberEps = 0.0000001;\n numerator: Decimal;\n denominator: Decimal;\n isSimplified = false;\n\n constructor(n: number | string | Decimal, d: number | string | Decimal = 1, isSimplified: boolean = false) {\n this.numerator = new Decimal(n);\n this.denominator = new Decimal(d);\n this.isSimplified = this.denominator.equals(1) || isSimplified;\n }\n\n static create(val: { numerator: number; denominator: number } | Fraction): Fraction {\n if (val instanceof Fraction) {\n return val.copy();\n }\n\n return Fraction.fromJSON(val);\n }\n\n static fromJSON(val: { numerator: number; denominator: number }): Fraction {\n return new Fraction(val.numerator, val.denominator);\n }\n\n static toCommonDenominator(\n first: Fraction,\n second: Fraction,\n ): {\n firstNumerator: Decimal;\n secondNumerator: Decimal;\n denominator: Decimal;\n } {\n if (first.denominator.equals(second.denominator)) {\n return {\n firstNumerator: first.numerator,\n secondNumerator: second.numerator,\n denominator: first.denominator,\n };\n } else {\n const firstNumerator = first.numerator.times(second.denominator);\n const secondNumerator = second.numerator.times(first.denominator);\n const resultDenominator = first.denominator.times(second.denominator);\n\n return {\n firstNumerator,\n secondNumerator,\n denominator: resultDenominator,\n };\n }\n }\n\n private static isNumber(value: Fraction | Decimal | number): value is number {\n return typeof value === 'number';\n }\n\n private static toInt(value: number): number {\n if (Math.ceil(value) - value < Fraction.numberEps) {\n // When value is like 1.99999999 => 2\n // 0.2+0.7+0.1 = 0.99999999 => 1\n return Math.ceil(value);\n }\n if (value - Math.floor(value) < Fraction.numberEps) {\n // When value is like 2.00000001 => 2\n return Math.floor(value);\n }\n\n return NaN;\n }\n\n private static numberToFraction(value: number): Fraction {\n const intVal = Fraction.toInt(value);\n if (!isNaN(intVal)) {\n return new Fraction(intVal);\n }\n const strValue = value.toString(10);\n const dotIndex = strValue.indexOf('.');\n const decimalLength = strValue.substring(dotIndex + 1).length;\n const denominator = Math.pow(10, decimalLength);\n const numerator = +strValue.replace('.', '');\n\n return new Fraction(numerator, denominator).simplify();\n }\n\n private static getCommonDivisor(a: Decimal, b: Decimal): Decimal {\n let temp: Decimal;\n while (!b.equals(0)) {\n temp = b;\n b = a.modulo(b);\n a = temp;\n }\n\n return a;\n }\n\n static empty(): Fraction {\n return new Fraction(0, 0, true);\n }\n\n static toFraction(value: Decimal | number | null): Fraction {\n if (value == null) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(value)) {\n // number path.\n return Fraction.numberToFraction(value);\n } else {\n if (value.lessThanOrEqualTo(0)) {\n return Fraction.empty();\n }\n const dp = value.decimalPlaces();\n const denominator = Math.pow(10, dp);\n\n return new Fraction(value.times(denominator), denominator).simplify();\n }\n }\n\n static fromString(value: string): Fraction {\n if (value.indexOf('/') === -1) {\n return Fraction.empty();\n }\n const values = value.split('/');\n const numerator = new Decimal(values[0]);\n const denominator = new Decimal(values[1]);\n\n return new Fraction(numerator, denominator);\n }\n\n add(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n const intVal = Fraction.toInt(other);\n if (!isNaN(intVal)) {\n // Go to fast path (no common denominator) where if we have an integer value.\n // We multiply the value with the denominator and add to the numerator\n // 2/3 + 2 = 2/3 + 6 / 3 = (2 + 6) / 3\n return new Fraction(this.numerator.add(this.denominator.times(intVal)), this.denominator);\n }\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n const convert = Fraction.toCommonDenominator(this, other);\n\n return new Fraction(convert.firstNumerator.plus(convert.secondNumerator), convert.denominator);\n }\n\n subtract(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n const intVal = Fraction.toInt(other);\n if (!isNaN(intVal)) {\n // Go to fast path (no common denominator) where if we have an integer value.\n // We multiply the value with the denominator and subtract from the numerator\n // 2/3 - 2 = 2/3 - 6 / 3 = (2 - 6) / 3\n return new Fraction(this.numerator.minus(this.denominator.times(intVal)), this.denominator);\n }\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n const convert = Fraction.toCommonDenominator(this, other);\n\n return new Fraction(convert.firstNumerator.minus(convert.secondNumerator), convert.denominator);\n }\n\n multiply(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n\n return new Fraction(this.numerator.times(other.numerator), this.denominator.times(other.denominator));\n }\n\n divide(other: Fraction | number): Fraction {\n if (this.isEmpty()) {\n return Fraction.empty();\n }\n if (Fraction.isNumber(other)) {\n other = Fraction.numberToFraction(other);\n }\n if (other.isEmpty()) {\n return Fraction.empty();\n }\n\n return new Fraction(this.numerator.times(other.denominator), this.denominator.times(other.numerator));\n }\n\n getValue(): Decimal {\n if (this.isEmpty()) {\n return new Decimal(0);\n }\n const n = new Decimal(this.numerator);\n const d = new Decimal(this.denominator);\n\n return n.dividedBy(d);\n }\n\n toString(): string {\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n return `${this.numerator}/${this.denominator}`;\n }\n\n simplify(): Fraction {\n if (this.isSimplified) {\n return this;\n } else {\n const greatestCommonDivisor = Fraction.getCommonDivisor(this.numerator, this.denominator);\n\n return new Fraction(this.numerator.dividedBy(greatestCommonDivisor), this.denominator.dividedBy(greatestCommonDivisor), true);\n }\n }\n\n /**\n * Compares fraction to another fraction or number\n *\n * @returns -1 if lower, 0 if equal, 1 if greater\n */\n compare(other: Fraction | number): number {\n const o = Fraction.isNumber(other) ? Fraction.toFraction(other) : other;\n const eq = Fraction.toCommonDenominator(this, o);\n\n return eq.firstNumerator.lessThan(eq.secondNumerator) ? -1 : eq.firstNumerator.greaterThan(eq.secondNumerator) ? 1 : 0;\n }\n\n lessThan(other: Fraction | number): boolean {\n return this.compare(other) === -1;\n }\n\n greaterThan(other: Fraction | number): boolean {\n return this.compare(other) === 1;\n }\n\n lessOrEqualThan(other: Fraction | number): boolean {\n return this.compare(other) <= 0;\n }\n\n greaterOrEqualThan(other: Fraction | number): boolean {\n return this.compare(other) >= 0;\n }\n\n equal(other: Fraction | number): boolean {\n return this.compare(other) === 0;\n }\n\n isEmpty(): boolean {\n return this.denominator.equals(0);\n }\n\n toJSON(): { numerator: number; denominator: number } {\n return {\n numerator: this.numerator.toNumber(),\n denominator: this.denominator.toNumber(),\n };\n }\n\n copy(): Fraction {\n return new Fraction(this.numerator, this.denominator, this.isSimplified);\n }\n}\n","import { Decimal } from 'decimal.js';\n\nimport { Fraction } from './fraction';\n\nexport interface Odds {\n decimals: number;\n fractional: { numerator: number; denominator: number };\n moneyline: number;\n}\n\nexport interface CalculatedOdds {\n decimals: Decimal;\n fractional: Fraction;\n moneyline: Decimal;\n}\n\nexport const emptyOdds: Odds = Object.freeze({\n decimals: 0,\n fractional: { numerator: 0, denominator: 0 },\n moneyline: 0,\n});\nexport const emptyCalculatedOdds: CalculatedOdds = {\n decimals: new Decimal(0),\n fractional: Fraction.empty(),\n moneyline: new Decimal(0),\n};\n","import { decimalOddsToAmerican } from '@frontend/sports/odds/feature/converters';\nimport { OddsDisplayFormat } from '@frontend/sports/types/models/user-settings';\nimport { Decimal } from 'decimal.js';\n\nimport { Fraction } from './fraction';\nimport type { CalculatedOdds, Odds } from './odds';\n\n/**\n * There are 3 main types of odds\n * 1. Decimals or European odds - a win based Odds * Stake = Total Possible payout\n * 2. Fractional or UK odds - a profit based Odds * Stake = Possible Profit ( Possible payout - Stake )\n * 3. Moneyline or US odds - a money based odds. Can have possitive or negative values.\n * When positive defines how much money will be the profit if the stake is 100.\n * When negative defines what should be the stake to have 100 profit\n */\nexport class OddsConverter {\n /**\n * Converting decimal to fration odds becomes by extracting 1 ( remove stake from the win) and convert to fraction\n *\n * @param decimals Decimal odds\n */\n static decimalToFraction(decimals: Decimal): Fraction {\n return Fraction.toFraction(decimals.minus(1)).simplify();\n }\n\n /**\n * Convert decimal to US by removing 1 ( to get the profit ) and if the result is:\n * - Greater or equal than 1 then multiply by 100\n * - Less than 1 then -100 / value\n *\n * @param decimals\n * @param precision\n */\n static decimalToUS(decimals: Decimal, precision: number = 10): Decimal {\n return decimalOddsToAmerican(decimals.toDecimalPlaces(precision));\n }\n\n /**\n * Convert fractional odds to decimal is by adding 1 ( add the stake ) and convert to decimal value\n *\n * @param fractionOdds\n */\n static fractionToDecimal(fractionOdds: Fraction): Decimal {\n return fractionOdds.add(1).getValue();\n }\n\n /**\n * Convert fractional to moneyline:\n * When fractional greater or equal to 1: Multiply by 100 and convert to decimals\n * When fractional less than 1 then we divide -100 to the fraction and convert to decimals\n *\n * @param fractionOdds\n * @param precision\n */\n static fractionToUS(fractionOdds: Fraction, precision: number = 2): Decimal {\n const us = fractionOdds.greaterOrEqualThan(1)\n ? fractionOdds.multiply(100).getValue()\n : new Fraction(100, 1).divide(fractionOdds).multiply(-1).getValue();\n\n return us.toDecimalPlaces(precision);\n }\n\n static usToDecimal(usOdds: Decimal, precision: number = 2): Decimal {\n return usOdds.lessThan(0)\n ? new Decimal(1).minus(new Decimal(100).dividedBy(usOdds).toDecimalPlaces(precision))\n : usOdds.dividedBy(100).add(1);\n }\n\n static usToFraction(usOdds: Decimal, precision: number = 2): Fraction {\n return usOdds.lessThan(0)\n ? Fraction.toFraction(new Decimal(-100).dividedBy(usOdds).toDecimalPlaces(precision))\n : Fraction.toFraction(usOdds.dividedBy(100));\n }\n\n /**\n * Convert decimal odds to the decimal value of the EW\n *\n * @param decimals\n * @param eachWay\n */\n static decimalToEachWay(decimals: Decimal, eachWay: Fraction): Decimal {\n return OddsConverter.decimalToFraction(decimals).multiply(eachWay).add(1).getValue();\n }\n\n /**\n * Convert decimal odds to the fractional value of the EW\n *\n * @param fracion\n * @param eachWay\n */\n static fractionToEachWay(fraction: Fraction, eachWay: Fraction): Fraction {\n return fraction.multiply(eachWay).simplify();\n }\n\n /**\n * Convert us odds to US value of each way\n */\n static usToEachWay(us: Decimal, eachWay: Fraction): Decimal {\n const fraction = OddsConverter.usToFraction(us, 7).multiply(eachWay).simplify();\n\n return OddsConverter.fractionToUS(fraction);\n }\n\n static convertOdds(\n targetFormat: OddsDisplayFormat,\n sourceFormat: OddsDisplayFormat,\n odds: Odds | CalculatedOdds,\n ): Partial | Partial {\n if (targetFormat === sourceFormat) {\n return odds;\n }\n switch (targetFormat) {\n case OddsDisplayFormat.EU: {\n if (sourceFormat === OddsDisplayFormat.UK) {\n return {\n decimals: OddsConverter.fractionToDecimal(Fraction.create(odds.fractional)).toNumber(),\n };\n }\n if (sourceFormat === OddsDisplayFormat.US) {\n return {\n decimals: OddsConverter.usToDecimal(new Decimal(odds.moneyline)).toNumber(),\n };\n }\n break;\n }\n case OddsDisplayFormat.UK: {\n if (sourceFormat === OddsDisplayFormat.EU) {\n return {\n fractional: OddsConverter.decimalToFraction(new Decimal(odds.decimals)).simplify().toJSON(),\n };\n }\n if (sourceFormat === OddsDisplayFormat.US) {\n return {\n fractional: OddsConverter.usToFraction(new Decimal(odds.moneyline)).simplify().toJSON(),\n };\n }\n break;\n }\n case OddsDisplayFormat.US: {\n if (sourceFormat === OddsDisplayFormat.EU) {\n return {\n moneyline: OddsConverter.decimalToUS(new Decimal(odds.decimals)).toNumber(),\n };\n }\n if (sourceFormat === OddsDisplayFormat.UK) {\n return {\n moneyline: OddsConverter.fractionToUS(Fraction.create(odds.fractional)).toNumber(),\n };\n }\n }\n }\n throw new Error('Invalid odds formats');\n }\n}\n","import { Odds as BposOdds, OddsFormat } from '@bpos';\nimport { Odds as BposCommonOdds } from '@bpos/common/bet-placement';\nimport { OddsDisplayFormat } from '@frontend/sports/types/models/user-settings';\nimport { Decimal } from 'decimal.js';\nimport { isArray } from 'lodash-es';\n\nimport { Fraction } from './fraction';\nimport { CalculatedOdds, Odds, emptyCalculatedOdds, emptyOdds } from './odds';\nimport { OddsConverter } from './odds-converter';\n\nexport class OddsOperations {\n static readonly OddsOne = OddsOperations.fromDecimalValue(new Decimal(1));\n\n static createOdds(price: { odds?: number; numerator?: number; denominator?: number; americanOdds?: number }): Odds {\n if (!price.odds && !price.numerator && !price.denominator && !price.americanOdds) {\n return emptyOdds;\n }\n\n return {\n decimals: price.odds || 0,\n fractional:\n price.numerator != null && price.denominator != null\n ? { numerator: price.numerator, denominator: price.denominator }\n : { numerator: 0, denominator: 0 },\n moneyline: price.americanOdds || 0,\n };\n }\n\n static createOdds2(odds: BposCommonOdds | undefined, oddsFormat: OddsFormat): Odds {\n if (!odds) return emptyOdds;\n switch (oddsFormat) {\n case OddsFormat.European: {\n return OddsOperations.createOdds({ odds: odds.european });\n }\n case OddsFormat.British: {\n return OddsOperations.createOdds({ numerator: odds.british.numerator, denominator: odds.british.denominator });\n }\n case OddsFormat.American: {\n return OddsOperations.createOdds({ americanOdds: odds.american });\n }\n default:\n return emptyOdds;\n }\n }\n\n static createOdds3(odds: BposOdds | undefined): Odds {\n switch (odds?.oddsFormat) {\n case OddsFormat.European: {\n return OddsOperations.createOdds({ odds: odds.european });\n }\n case OddsFormat.British: {\n return OddsOperations.createOdds({ numerator: odds.british.numerator, denominator: odds.british.denominator });\n }\n case OddsFormat.American: {\n return OddsOperations.createOdds({ americanOdds: odds.american });\n }\n default:\n return emptyOdds;\n }\n }\n\n static isOddsEmpty(odds: Odds | CalculatedOdds): boolean {\n return odds === emptyOdds || odds === emptyCalculatedOdds;\n }\n\n static isOddsValid(odds: Odds | CalculatedOdds): boolean {\n if (OddsOperations.isOddsEmpty(odds)) {\n return false;\n }\n\n return (\n OddsOperations.isDecimalOddsValid(odds.decimals) ||\n OddsOperations.isFractionalOddsValid(odds.fractional) ||\n OddsOperations.isMoneyLineOddsValid(odds.moneyline)\n );\n }\n\n static isDecimalOddsValid(decimals: number | Decimal): boolean {\n return typeof decimals === 'number' ? decimals > 1 : decimals.greaterThan(1);\n }\n\n static isFractionalOddsValid(fractional: { numerator: number; denominator: number } | Fraction): boolean {\n return OddsConverter.fractionToDecimal(Fraction.create(fractional)).greaterThan(1);\n }\n\n static isMoneyLineOddsValid(moneyline: number | Decimal): boolean {\n if (typeof moneyline === 'number') {\n return moneyline >= 100 || (moneyline < -100 && moneyline >= -100000); // I think -100000 is quite smaller number we don't have so small odds\n }\n\n return moneyline.greaterThanOrEqualTo(100) || (moneyline.lessThan(-100) && moneyline.greaterThan(-100000));\n }\n\n static sum(odds: CalculatedOdds[]): CalculatedOdds {\n if (odds.length === 1) {\n return OddsOperations.isOddsEmpty(odds[0]) ? emptyCalculatedOdds : odds[0];\n }\n\n let decimals = new Decimal(0);\n const initialValue = new Fraction(0, 1);\n let fractional = initialValue;\n let moneyline = new Decimal(0);\n for (const odd of odds) {\n if (OddsOperations.isOddsEmpty(odd)) {\n return emptyCalculatedOdds;\n }\n\n decimals = decimals.add(odd.decimals);\n fractional = fractional === initialValue ? odd.fractional : fractional.add(1).add(odd.fractional.add(1)).subtract(1);\n moneyline = moneyline.add(OddsConverter.usToDecimal(odd.moneyline, 10));\n }\n\n return {\n decimals,\n fractional,\n moneyline: OddsConverter.decimalToUS(moneyline),\n };\n }\n\n static getTotalOdds(odds: CalculatedOdds[] | CalculatedOdds, round: boolean): CalculatedOdds {\n return OddsOperations.multiply(isArray(odds) ? odds : [odds, this.OddsOne], round);\n }\n\n static multiply(odds: CalculatedOdds[], round: boolean): CalculatedOdds {\n if (odds.length === 1) {\n return OddsOperations.isOddsEmpty(odds[0]) ? emptyCalculatedOdds : odds[0];\n }\n\n let decimals = new Decimal(1);\n let fractional = new Fraction(0, 1);\n let moneyline = new Decimal(1);\n for (const odd of odds) {\n if (OddsOperations.isOddsEmpty(odd)) {\n return emptyCalculatedOdds;\n }\n\n decimals = decimals.times(odd.decimals);\n fractional = fractional.add(1).multiply(odd.fractional.add(1)).subtract(1);\n moneyline = moneyline.times(OddsConverter.usToDecimal(odd.moneyline, 10));\n }\n\n return {\n decimals: round ? decimals.toDecimalPlaces(2, Decimal.ROUND_HALF_UP) : decimals,\n fractional,\n moneyline: OddsConverter.decimalToUS(moneyline),\n };\n }\n\n static equal(first: Odds, second: Odds, oddsFormat: OddsFormat = OddsFormat.None): boolean {\n switch (oddsFormat) {\n case OddsFormat.British:\n return Fraction.fromJSON(first.fractional).equal(Fraction.fromJSON(second.fractional));\n case OddsFormat.European:\n return first.decimals === second.decimals;\n case OddsFormat.American:\n return first.moneyline === second.moneyline;\n default:\n return (\n first.decimals === second.decimals &&\n Fraction.fromJSON(first.fractional).equal(Fraction.fromJSON(second.fractional)) &&\n first.moneyline === second.moneyline\n );\n }\n }\n\n static equal2(first: CalculatedOdds, second: CalculatedOdds): boolean {\n return first.decimals.equals(second.decimals) && first.fractional.equal(second.fractional) && first.moneyline.equals(second.moneyline);\n }\n\n static toCalculatedOdds(nativeOdds: Odds | undefined): CalculatedOdds {\n if (!nativeOdds) {\n return emptyCalculatedOdds;\n }\n\n const calculatedOdds = {\n decimals: nativeOdds.decimals ? new Decimal(nativeOdds.decimals) : new Decimal(0),\n fractional: nativeOdds.fractional ? Fraction.fromJSON(nativeOdds.fractional) : Fraction.empty(),\n moneyline: nativeOdds.moneyline ? new Decimal(nativeOdds.moneyline) : new Decimal(0),\n };\n\n //Return emptyCalculatedOdds if the calculated odds is equal to it to make future comparison accurate\n //e.g. isOddsEmpty should return true in this case\n if (this.equal2(calculatedOdds, emptyCalculatedOdds)) {\n return emptyCalculatedOdds;\n }\n\n return calculatedOdds;\n }\n\n static toDecimalValue(odds: CalculatedOdds, format?: OddsDisplayFormat): Decimal {\n switch (format) {\n case OddsDisplayFormat.UK:\n return OddsConverter.fractionToDecimal(odds.fractional);\n case OddsDisplayFormat.US:\n return OddsConverter.usToDecimal(odds.moneyline, 10);\n default:\n return odds.decimals;\n }\n }\n\n static fromDecimalValue(decimals: Decimal): CalculatedOdds {\n return {\n decimals,\n fractional: OddsConverter.decimalToFraction(decimals),\n moneyline: OddsConverter.decimalToUS(decimals),\n };\n }\n\n static fromDecimalsToOdds(value: number): Odds {\n return OddsOperations.createOdds({\n odds: value,\n numerator: new Decimal(value).minus(1).toNumber(),\n denominator: 1,\n });\n }\n\n static convertOddsFormat(oddsFormat: OddsFormat): OddsDisplayFormat {\n switch (oddsFormat) {\n case OddsFormat.American:\n return OddsDisplayFormat.US;\n case OddsFormat.British:\n return OddsDisplayFormat.UK;\n case OddsFormat.European:\n return OddsDisplayFormat.EU;\n case OddsFormat.None:\n return OddsDisplayFormat.EU;\n }\n }\n\n static convertToBposOddsFormat(oddsFormat: OddsDisplayFormat = OddsDisplayFormat.EU): OddsFormat {\n switch (oddsFormat) {\n case OddsDisplayFormat.EU:\n return OddsFormat.European;\n case OddsDisplayFormat.UK:\n return OddsFormat.British;\n case OddsDisplayFormat.US:\n return OddsFormat.American;\n }\n }\n\n static toOdds(nativeOdds: CalculatedOdds): Odds {\n return {\n decimals: nativeOdds.decimals ? nativeOdds.decimals.toNumber() : 0,\n\n fractional: nativeOdds.fractional\n ? { numerator: nativeOdds.fractional.numerator.toNumber(), denominator: nativeOdds.fractional.denominator.toNumber() }\n : { numerator: 0, denominator: 0 },\n\n moneyline: nativeOdds.moneyline ? nativeOdds.moneyline.toNumber() : 0,\n };\n }\n\n static lessThan(first: CalculatedOdds, second: CalculatedOdds, oddsFormat: OddsFormat = OddsFormat.None): boolean {\n switch (oddsFormat) {\n case OddsFormat.British:\n return first.fractional.lessThan(second.fractional);\n case OddsFormat.American:\n return first.moneyline.lessThan(second.moneyline);\n default:\n return first.decimals.lessThan(second.decimals);\n }\n }\n\n static greaterThan(first: CalculatedOdds, second: CalculatedOdds, oddsFormat: OddsFormat = OddsFormat.None): boolean {\n switch (oddsFormat) {\n case OddsFormat.British:\n return first.fractional.greaterThan(second.fractional);\n case OddsFormat.American:\n return first.moneyline.greaterThan(second.moneyline);\n default:\n return first.decimals.greaterThan(second.decimals);\n }\n }\n}\n","import { FixtureViewType, TotalsPrefix } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { OddsOperations } from '@frontend/sports/odds/feature';\n\nimport { BetslipGroupInformation } from '../groups/betslip-group-information';\nimport { PickId, pickIdFactory } from './pick-id';\nimport {\n IAcceptedPrice,\n IBetslipPickMarket,\n IBetslipPickStorage,\n IBetslipV2Option,\n IBoostedPrice,\n IPickTracking,\n IPrice,\n PickOddsState,\n PickSubType,\n PriceType,\n ResultStatus,\n} from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport abstract class BetslipPick {\n id: PickId;\n\n // is the pick in the betting offer\n isAvailable = true;\n\n /**\n * Pick result status. It can be set to something else after trying to do bet placement and the pick is resulted.\n * When result status is !== of OPEN then pick is treated as closed.\n */\n resultStatus: ResultStatus = ResultStatus.Open;\n\n priceType: PriceType;\n\n /* Accepted odds from the customer, when current price type is Fixed when price is not fixed accepted Odds are undefined. */\n acceptedPrice: IAcceptedPrice;\n\n boostedPriceDetails: IBoostedPrice | undefined;\n\n market: IBetslipPickMarket;\n\n priceHistory: IPrice[];\n\n subscriptionContext: string[] = [];\n\n tracking: IPickTracking;\n\n contextualInfo?: string;\n\n liveAlert?: boolean;\n\n parentLinkedEventId?: string;\n\n siblingOption?: IBetslipV2Option;\n\n /**\n * Indicates that the pick is part of the new customer offer (hidden market)\n */\n isNewCustomerOfferPick: boolean;\n\n highlighted?: boolean;\n\n totalsPrefix?: TotalsPrefix;\n fromHybridFixture?: boolean;\n protected _groupInfo?: BetslipGroupInformation;\n\n get groupInfo() {\n return this._groupInfo;\n }\n\n protected constructor() {\n this.priceHistory = [];\n }\n\n protected abstract isPriceVisible(): boolean;\n\n abstract isMarketVisible(): boolean;\n\n private isVisible(): boolean {\n return this.isAvailable && this.isPriceVisible() && this.isMarketVisible();\n }\n\n protected isOpen(): boolean {\n return this.resultStatus === ResultStatus.Open;\n }\n\n get currentPrice(): IPrice | undefined {\n return this.prices[0];\n }\n\n get oddsState(): PickOddsState {\n if (!this.isOpen()) {\n return PickOddsState.Closed;\n }\n\n if (!this.isVisible()) {\n return PickOddsState.Locked;\n }\n\n return PickOddsState.Open;\n }\n\n abstract get isLive(): boolean;\n\n abstract copy(): BetslipPick;\n\n // abstract updateFromMessage(m: MessageEnvelope): void;\n\n abstract get prices(): IPrice[];\n\n abstract get eventName(): SignedName;\n\n abstract get marketName(): SignedName;\n\n abstract get optionName(): SignedName;\n\n abstract get sportId(): SportConstant;\n\n abstract get isVirtual(): boolean;\n\n abstract get eventDate(): Date;\n\n abstract get regionName(): SignedName;\n\n abstract get competitionName(): SignedName;\n\n abstract get competitionId(): number;\n\n abstract get sportName(): SignedName;\n\n abstract get leagueName(): SignedName;\n\n abstract get partitionId(): number | undefined;\n\n abstract get eventParticipants(): EventParticipant[] | undefined;\n\n abstract get eventViewType(): FixtureViewType | undefined;\n\n betCount(): number {\n return 1;\n }\n\n /**\n * toJSON func should do a deep copy of the object to storage, we need that because when we do copy we get toJSON result and init pick from it.,\n */\n toJSON(): IBetslipPickStorage {\n return {\n id: this.id.toString(),\n priceHistory: this.priceHistory,\n priceType: this.priceType,\n acceptedPrice: this.acceptedPrice,\n isNewCustomerOffer: this.isNewCustomerOfferPick,\n market: this.market\n ? {\n id: this.market.id,\n isVisible: this.market.isVisible,\n isBetBuilderEnabled: this.market.isBetBuilderEnabled,\n }\n : null,\n subscriptionContext: this.subscriptionContext,\n pickSubType: PickSubType.None,\n tracking: this.tracking,\n boostedPriceDetails: this.boostedPriceDetails,\n liveAlert: this.liveAlert,\n groupInfo: this.groupInfo,\n parentLinkedEventId: this.parentLinkedEventId ?? this.id.parentLinkedEventId,\n totalsPrefix: this.totalsPrefix,\n fromHybridFixture: this.fromHybridFixture,\n };\n }\n\n protected initPropertiesFromJSON(value: IBetslipPickStorage): void {\n this.id = pickIdFactory(value.id);\n this.priceHistory = value.priceHistory;\n this.priceType = value.priceType;\n this.acceptedPrice = value.acceptedPrice;\n this.isNewCustomerOfferPick = value.isNewCustomerOffer;\n this.subscriptionContext = value.subscriptionContext;\n if (value.market) {\n this.market = {\n id: value.market.id,\n isVisible: value.market.isVisible,\n isBetBuilderEnabled: value.market.isBetBuilderEnabled,\n };\n }\n this.tracking = value.tracking ?? {};\n this.boostedPriceDetails = value.boostedPriceDetails;\n this.liveAlert = value.liveAlert;\n this._groupInfo = value.groupInfo;\n\n this.parentLinkedEventId = value.parentLinkedEventId;\n this.totalsPrefix = value.totalsPrefix;\n this.fromHybridFixture = value.fromHybridFixture;\n }\n\n getOddsAcceptance(): boolean {\n if (this.currentPrice?.nativeOdds === this.acceptedPrice.price?.nativeOdds) {\n return true;\n } else if (!this.currentPrice?.nativeOdds || !this.acceptedPrice.price?.nativeOdds) {\n return false;\n }\n\n return OddsOperations.equal(this.currentPrice?.nativeOdds, this.acceptedPrice.price?.nativeOdds);\n }\n\n setGroupInfo(groupInfo: BetslipGroupInformation) {\n this._groupInfo = groupInfo;\n }\n}\n","import { getPartition } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\n\nimport { BetslipPick } from './betslip-pick';\nimport { V2PickId } from './pick-id';\nimport { IBetslipV2PickFixture, IBetslipV2PickStorage, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport abstract class BetslipV2Pick extends BetslipPick {\n override id: V2PickId;\n fixture: IBetslipV2PickFixture;\n\n static isPick(pick: BetslipPick): pick is BetslipV2Pick {\n return pick instanceof BetslipV2Pick;\n }\n\n protected constructor() {\n super();\n this.priceType = PriceType.Fixed;\n }\n\n isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n protected isPriceVisible(): boolean {\n return true;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2PickStorage): void {\n super.initPropertiesFromJSON(value);\n this.fixture = {\n ...value.fixture,\n eventDate: new Date(value.fixture.eventDate),\n cutOffDate: new Date(value.fixture.cutOffDate),\n league: value.fixture.league,\n region: value.fixture.region,\n };\n }\n\n get leagueId(): number {\n if (this.fixture.league !== null) {\n return this.fixture.league.id;\n }\n\n return 0;\n }\n\n get leagueName(): SignedName {\n return (\n this.fixture.league?.name || {\n name: '',\n signature: '',\n }\n );\n }\n\n get regionId(): number {\n if (this.fixture.region !== null) {\n return this.fixture.region.id;\n }\n\n return 0;\n }\n\n get regionName(): SignedName {\n return (\n this.fixture.region?.name || {\n name: '',\n signature: '',\n }\n );\n }\n\n override isOpen(): boolean {\n if (!super.isOpen()) {\n return false;\n }\n\n return this.fixture.cutOffDate > new Date();\n }\n\n get eventName(): SignedName {\n return this.fixture.name;\n }\n\n get sportId(): SportConstant {\n return this.fixture.sportId;\n }\n\n get partitionId(): number | undefined {\n return getPartition(this.fixture.fixtureId);\n }\n\n get sportName(): SignedName {\n return this.fixture.sportName;\n }\n\n get eventDate(): Date {\n return this.fixture.eventDate;\n }\n\n get competitionName(): SignedName {\n return this.leagueName;\n }\n\n get competitionId(): number {\n return this.leagueId;\n }\n\n abstract override get isLive(): boolean;\n\n get isVirtual(): boolean {\n return this.fixture.virtual;\n }\n\n override toJSON(): IBetslipV2PickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n fixture: {\n ...this.fixture,\n eventDate: this.fixture.eventDate.toISOString(),\n cutOffDate: this.fixture.cutOffDate.toISOString(),\n league: this.fixture.league && {\n id: this.fixture.league.id,\n name: { ...this.fixture.league.name },\n realCompetitionId: this.fixture.league.realCompetitionId,\n },\n region: this.fixture.region && {\n id: this.fixture.region.id,\n name: { ...this.fixture.region.name },\n },\n },\n };\n }\n\n setMarketInvisible(): void {\n this.market.isVisible = false;\n }\n}\n","import { FixtureStage, FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { cloneDeep } from 'lodash-es';\n\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2Pick } from './betslip-v2-pick';\nimport { V2OptionMarketPickId } from './pick-id';\nimport { IBetslipV2Option, IBetslipV2OptionMarket, IBetslipV2OptionMarketPickStorage, IPlaceTerms, IPrice, ParticipantPickType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\n/**\n * General Option Market Pick\n */\nexport abstract class BetslipV2OptionMarketPick extends BetslipV2Pick {\n override market: IBetslipV2OptionMarket;\n option: IBetslipV2Option;\n options: IBetslipV2Option[];\n override id: V2OptionMarketPickId;\n isFromNewCustomerOffer: boolean;\n override siblingOption?: IBetslipV2Option;\n pickType?: ParticipantPickType;\n isFavouritePick?: boolean;\n\n constructor() {\n super();\n }\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.fixture.participants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.fixture.viewType;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2OptionMarketPick {\n return pick instanceof BetslipV2OptionMarketPick;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2OptionMarketPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.market = value.market;\n this.isFavouritePick = value.isFavouritePick;\n this.isFromNewCustomerOffer = value.isFromNewCustomerOffer || false;\n\n this.option = {\n ...value.option,\n prices: cloneDeep(value.option.prices),\n isDraw: value.option.isDraw,\n };\n this.pickType = value.pickType;\n this.siblingOption = value.siblingOption;\n }\n\n get optionId(): number {\n return this.option.id;\n }\n\n get optionName(): SignedName {\n return this.option.name;\n }\n\n get marketName(): SignedName {\n return this.market.name;\n }\n\n get isLive(): boolean {\n return this.fixture.stage === FixtureStage.Live;\n }\n\n get isEachWay(): boolean {\n return this.market.isEachWay;\n }\n\n get placeTerms(): IPlaceTerms | null {\n return this.market.placeTerms;\n }\n\n get realCompetitionId(): number | undefined {\n return this.fixture.league?.realCompetitionId;\n }\n\n override isPriceVisible(): boolean {\n return this.option.isVisible && !!this.currentPrice && this.currentPrice.isVisible;\n }\n\n override isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n override isOpen(): boolean {\n if (!super.isOpen() || this.market.isClosed) {\n return false;\n }\n\n return this.fixture.cutOffDate > new Date();\n }\n\n override toJSON(): IBetslipV2OptionMarketPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n id: this.id.toString(),\n isFromNewCustomerOffer: this.isFromNewCustomerOffer,\n market: {\n ...this.market,\n },\n isFavouritePick: this.isFavouritePick,\n option: {\n ...this.option,\n },\n pickType: this.pickType,\n siblingOption: this.siblingOption!,\n };\n }\n\n get prices(): IPrice[] {\n return this.option.prices.filter((pr) => pr.marketId === this.market.id);\n }\n\n abstract override copy(): BetslipV2OptionMarketPick;\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\n\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2OptionMarketPick } from './betslip-v2-option-market-pick';\nimport { BetBuilderPickId } from './pick-id';\nimport { IBetslipBetBuilderPickStorage, IEventData, PickSubType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport interface SportcastOption {\n name: string;\n longId: string;\n}\n\nexport class BetslipBetBuilderPick extends BetslipV2OptionMarketPick {\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.event.event.participants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.event.event.viewType;\n }\n\n override id: BetBuilderPickId;\n eventId: string;\n event: IEventData;\n sportcastOptions: SportcastOption[];\n sportcastId?: number;\n betgeniusId?: number;\n betSlipUid?: string;\n useV2Key?: boolean;\n longIds?: string[];\n\n static override isPick(pick: BetslipPick): pick is BetslipBetBuilderPick {\n return pick instanceof BetslipBetBuilderPick;\n }\n\n static isEntainUiBetBuilderPick(pick: BetslipPick): pick is BetslipBetBuilderPick & boolean {\n return pick instanceof BetslipBetBuilderPick && !!pick.sportcastOptions?.length;\n }\n\n getLegCount(): number {\n return this.sportcastOptions?.length ? this.sportcastOptions.length : this.market.name.name.split(';').length;\n }\n\n static fromJSON(value: IBetslipBetBuilderPickStorage): BetslipBetBuilderPick {\n const pick = new BetslipBetBuilderPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipBetBuilderPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.eventId = value.eventId;\n this.event = value.event && {\n ...value.event,\n event: {\n ...value.event.event,\n date: new Date(value.event.event.date),\n },\n };\n this.sportcastOptions = value.sportcastOptions;\n this.betgeniusId = value.betgeniusId;\n this.sportcastId = value.sportcastId;\n this.betSlipUid = value.betSlipUid;\n this.useV2Key = value.useV2key;\n this.longIds = value.longIds;\n }\n\n getLegs(): string[] {\n return this.market.name.name.split(';');\n }\n\n override betCount(): number {\n return 1;\n }\n\n override toJSON(): IBetslipBetBuilderPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n eventId: this.eventId,\n event: this.event,\n pickSubType: PickSubType.BetBuilderPick,\n sportcastOptions: this.sportcastOptions,\n betgeniusId: this.betgeniusId,\n sportcastId: this.sportcastId,\n betSlipUid: this.betSlipUid,\n useV2key: this.useV2Key,\n longIds: this.longIds,\n };\n }\n\n copy(): BetslipBetBuilderPick {\n const copy = this.toJSON();\n\n return BetslipBetBuilderPick.fromJSON(copy);\n }\n\n override get regionName(): SignedName {\n return this.event.region.name;\n }\n\n override get leagueName(): SignedName {\n return this.event.league.name;\n }\n\n override get eventDate(): Date {\n return this.event.event.date;\n }\n}\n","export enum BetslipPickChangeType {\n OddsChange,\n VisibilityChange,\n LockChange,\n}\n\nexport interface BetslipPickChangeInfo {\n changeType?: BetslipPickChangeType;\n}\n","import { ComboPreventionType } from '@cds/betting-offer';\n\nexport enum CombinationPrevention {\n /// \n /// No value was chosen for ComboPrevention.\n /// \n Default = 0,\n\n /// \n /// This bet can be combined with any other bet.\n /// \n AllCombo = 1,\n\n /// \n /// This bet cannot be combined with any other bet.\n /// \n NoCombo = 2,\n\n /// \n /// This bet cannot be combined with a bet from the same sport.\n /// \n NoSportCombo = 3,\n\n /// \n /// This bet cannot be combined with a bet from the same league.\n /// \n NoLeagueCombo = 4,\n\n /// \n /// This bet cannot be combined with a bet from the same event.\n /// \n NoEventCombo = 6,\n\n /// \n /// This bet can only be combined with a bet from the same league but a different event.\n /// \n OnlySameLeagueDifferentEvent = 7,\n\n /// \n /// This bet can only be combined with live bets\n /// \n OnlyLiveEvents = 8,\n\n /// \n /// This bet can only be combined with live bets from a different event.\n /// \n OnlyDifferentLiveEvents = 9,\n\n /// \n /// This bet cannot be combined with a bet from the same competition.\n /// \n NoCompetitionCombo = 10,\n\n /// \n /// This bet cannot be combined with a bet from the same fixture.\n /// \n NoFixtureCombo = 11,\n\n /// \n /// This bet cannot be combined with a bet from the same competition group.\n /// \n NoGroupCombo = 12,\n}\n\nexport function toComboPrevention(status: ComboPreventionType): CombinationPrevention {\n switch (status) {\n case ComboPreventionType.AllCombo:\n return CombinationPrevention.AllCombo;\n case ComboPreventionType.AllLiveEvents:\n return CombinationPrevention.OnlyLiveEvents;\n case ComboPreventionType.NoCombo:\n return CombinationPrevention.NoCombo;\n case ComboPreventionType.NoEventCombo:\n return CombinationPrevention.NoEventCombo;\n case ComboPreventionType.NoFixtureCombo:\n return CombinationPrevention.NoFixtureCombo;\n case ComboPreventionType.NoEventSameLeagueCombo:\n return CombinationPrevention.OnlySameLeagueDifferentEvent;\n case ComboPreventionType.NoCompetitionCombo:\n return CombinationPrevention.NoCompetitionCombo;\n case ComboPreventionType.NoLeagueCombo:\n return CombinationPrevention.NoLeagueCombo;\n case ComboPreventionType.NoSportCombo:\n return CombinationPrevention.NoSportCombo;\n case ComboPreventionType.OtherLiveEvents:\n return CombinationPrevention.OnlyDifferentLiveEvents;\n case ComboPreventionType.NoGroupCombo:\n return CombinationPrevention.NoGroupCombo;\n }\n}\n","import { ComboPreventionType2 } from '@cds/betting-offer';\n\nexport enum MinimumCombination {\n /// \n /// No value was chosen for MinimumCombo.\n /// \n Default = 0,\n\n /// \n /// There must be at least one bet in the betslip (the current bet counts,\n /// so this effectively says that the current bet can be used on its own).\n /// \n Single = 1,\n\n /// \n /// There must be at least two bets in the betslip.\n /// \n Combo2 = 2,\n\n /// \n /// There must be at least three bets in the betslip.\n /// \n Combo3 = 3,\n\n /// \n /// There must be at least four bets in the betslip.\n /// \n Combo4 = 4,\n\n /// \n /// There must be at least five bets in the betslip.\n /// \n Combo5 = 5,\n\n /// \n /// There must be at least six bets in the betslip.\n /// \n Combo6 = 6,\n}\n\nexport function toMinimumCombo(status: ComboPreventionType2 | number): MinimumCombination {\n switch (status) {\n case ComboPreventionType2.Combo2:\n return MinimumCombination.Combo2;\n case ComboPreventionType2.Combo3:\n return MinimumCombination.Combo3;\n case ComboPreventionType2.Combo4:\n return MinimumCombination.Combo4;\n case ComboPreventionType2.Combo5:\n return MinimumCombination.Combo5;\n case ComboPreventionType2.Combo6:\n return MinimumCombination.Combo6;\n }\n\n return MinimumCombination.Single;\n}\n\nexport function toMinimumComboTV2(status?: number): MinimumCombination {\n if (!status) {\n return MinimumCombination.Single;\n }\n\n switch (status) {\n case 2:\n return MinimumCombination.Combo2;\n case 3:\n return MinimumCombination.Combo3;\n case 4:\n return MinimumCombination.Combo4;\n case 5:\n return MinimumCombination.Combo5;\n case 6:\n return MinimumCombination.Combo6;\n }\n\n return MinimumCombination.Single;\n}\n","import { FixtureViewType, Visibility } from '@cds/betting-offer';\nimport { MessageEnvelope, MessageType } from '@cds/push';\nimport { GameUpdateCommand } from '@cds/push/fixture-commands';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { GameDeleteMessageEnvelope, GameUpdateMessageEnvelope } from '@frontend/sports/content-distribution/feature';\nimport { OddsOperations } from '@frontend/sports/odds/feature';\n\nimport { BetslipPickChangeInfo, BetslipPickChangeType } from '../../modules/validation/picks/betslip-pick-change-info';\nimport { BetslipPick } from './betslip-pick';\nimport { toComboPrevention } from './combo-prevention';\nimport { toMinimumCombo } from './minimum-combination';\nimport { V1PickId } from './pick-id';\nimport {\n BetslipV1PickEvent,\n BetslipV1PickLeague,\n BetslipV1PickMarket,\n BetslipV1PickOption,\n BetslipV1PickRegion,\n BetslipV1PickSport,\n BetslipV1PickTeaserData,\n IBetslipV1PickDto,\n IBetslipV1PickStorage,\n IPrice,\n PickSubType,\n PriceType,\n} from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nfunction isGameDeleteEnvelope(envelope: MessageEnvelope): envelope is GameDeleteMessageEnvelope {\n return envelope.messageType === MessageType.GameDelete;\n}\n\nfunction isGameUpdateEnvelope(envelope: MessageEnvelope): envelope is GameUpdateMessageEnvelope {\n return !!(envelope.messageType === MessageType.GameUpdate && (envelope.payload as GameUpdateCommand).game);\n}\n\nexport class BetslipV1Pick extends BetslipPick implements IBetslipV1PickDto {\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.event.participants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.event.viewType;\n }\n\n override id: V1PickId;\n sport: BetslipV1PickSport;\n league: BetslipV1PickLeague;\n region: BetslipV1PickRegion;\n event: BetslipV1PickEvent;\n override market: BetslipV1PickMarket;\n option: BetslipV1PickOption;\n teaser: BetslipV1PickTeaserData;\n\n static fromJSON(value: IBetslipV1PickStorage): BetslipV1Pick {\n const pick = new BetslipV1Pick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n static isPick(pick: BetslipPick): pick is BetslipV1Pick {\n return pick instanceof BetslipV1Pick;\n }\n\n constructor() {\n super();\n this.priceType = PriceType.Fixed;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV1PickStorage): void {\n super.initPropertiesFromJSON(value);\n this.event = {\n ...value.event,\n eventDate: new Date(value.event.eventDate),\n cutOffDate: new Date(value.event.cutOffDate),\n };\n this.league = value.league;\n this.region = value.region;\n this.sport = value.sport;\n this.market = {\n ...value.market,\n };\n this.option = {\n ...value.option,\n };\n this.teaser = {\n ...value.teaser,\n };\n }\n\n get eventName(): SignedName {\n return this.event.name;\n }\n\n get marketName(): SignedName {\n return this.market.name;\n }\n\n get optionName(): SignedName {\n return this.option.name;\n }\n\n get optionId(): number {\n return this.option.id;\n }\n\n get sportId(): SportConstant {\n return this.sport.id;\n }\n\n get partitionId(): number | undefined {\n return undefined;\n }\n\n get isVirtual(): boolean {\n return false;\n }\n\n get eventDate(): Date {\n return this.event.eventDate;\n }\n\n get regionName(): SignedName {\n return this.region.name;\n }\n\n get competitionName(): SignedName {\n return this.league.name;\n }\n\n get competitionId(): number {\n return this.league.id;\n }\n\n get sportName(): SignedName {\n return this.sport.name;\n }\n\n get leagueName(): SignedName {\n return this.league.name;\n }\n\n isPriceVisible(): boolean {\n return !!this.currentPrice && this.currentPrice.isVisible;\n }\n\n isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n setMarketInvisible(): void {\n this.market.isVisible = false;\n }\n\n /**\n * Is pick open.\n * Pick is open when:\n * 1. Is result status is OPEN.\n * 2. Is cutoff date is in future.\n */\n override isOpen(): boolean {\n if (!super.isOpen() || this.market.isClosed) {\n return false;\n }\n\n return this.event.cutOffDate > new Date();\n }\n\n override toJSON(): IBetslipV1PickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n id: this.id.toString(),\n pickSubType: PickSubType.V1Pick,\n event: {\n id: this.event.id,\n name: { ...this.event.name },\n groupId: this.event.groupId,\n isLive: this.event.isLive,\n isPublished: this.event.isPublished,\n eventDate: this.event.eventDate.toISOString(),\n cutOffDate: this.event.cutOffDate.toISOString(),\n participants: this.event.participants,\n viewType: this.event.viewType,\n },\n league: {\n ...this.league,\n },\n region: {\n ...this.region,\n },\n sport: {\n ...this.sport,\n },\n market: {\n ...this.market,\n },\n option: {\n ...this.option,\n },\n teaser: {\n ...this.teaser,\n },\n };\n }\n\n copy(): BetslipV1Pick {\n const storage = this.toJSON();\n\n return BetslipV1Pick.fromJSON(storage);\n }\n\n updateFromMessage(m: MessageEnvelope): BetslipPickChangeInfo {\n const result: BetslipPickChangeInfo = {};\n\n if (isGameDeleteEnvelope(m) && m.payload.gameId === this.market.id) {\n this.market = { ...this.market, isVisible: false, isClosed: true };\n\n return result;\n }\n if (isGameUpdateEnvelope(m) && m.payload.game.id === this.market.id && m.payload.game.results.some((r) => r.id === this.option.id)) {\n const cdsGame = m.payload.game;\n this.market = {\n ...this.market,\n isVisible: cdsGame.visibility === Visibility.Visible,\n isClosed: cdsGame.visibility === Visibility.Hidden,\n comboPrevention: toComboPrevention(cdsGame.combo1),\n minimumCombo: toMinimumCombo(cdsGame.combo2),\n };\n\n const option = cdsGame.results.find((o) => o.id === this.option.id)!;\n const newPrice = {\n ...this.currentPrice!,\n nativeOdds: OddsOperations.createOdds(option),\n isVisible: option.visibility === Visibility.Visible && option.odds > 1,\n };\n if (!OddsOperations.equal(newPrice.nativeOdds, this.currentPrice!.nativeOdds) || newPrice.isVisible !== this.currentPrice!.isVisible) {\n this.priceHistory.push({ ...this.currentPrice! });\n if (!OddsOperations.equal(newPrice.nativeOdds, this.currentPrice!.nativeOdds)) {\n result.changeType = BetslipPickChangeType.OddsChange;\n } else if (newPrice.isVisible !== this.currentPrice!.isVisible) {\n result.changeType = BetslipPickChangeType.VisibilityChange;\n }\n }\n this.option.price = newPrice;\n }\n\n return result;\n }\n\n get isLive(): boolean {\n return this.event.isLive;\n }\n\n get prices(): IPrice[] {\n return [this.option.price];\n }\n}\n","import { BetslipV2OptionMarketPick } from './betslip-v2-option-market-pick';\nimport { IBetslipV2OptionMarketPickStorage, PickSubType } from './pick-models';\n\nexport class BetslipV2StandardPick extends BetslipV2OptionMarketPick {\n static fromJSON(value: IBetslipV2OptionMarketPickStorage): BetslipV2OptionMarketPick {\n const pick = new BetslipV2StandardPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n override toJSON(): IBetslipV2OptionMarketPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n pickSubType: PickSubType.StandardV2Pick,\n };\n }\n\n copy(): BetslipV2OptionMarketPick {\n const storage = this.toJSON();\n\n return BetslipV2StandardPick.fromJSON(storage);\n }\n}\n","import { OptionMarket, ParticipantOption, ParticipantPriceStatus, ParticipantPriceType, Price } from '@cds/betting-offer';\nimport { OddsOperations, emptyOdds } from '@frontend/sports/odds/feature';\n\nimport { IPrice, PriceType } from './picks/pick-models';\n\nexport function participantOptionPriceToBetslipPrice(option: ParticipantOption): IPrice {\n return {\n id: option.price.id,\n isVisible:\n option.participantPriceStatus === ParticipantPriceStatus.Visible &&\n (option.priceType !== ParticipantPriceType.Fixed || OddsOperations.isOddsValid(OddsOperations.createOdds(option.price))),\n marketId: option.marketId,\n nativeOdds: option.priceType === ParticipantPriceType.Fixed ? OddsOperations.createOdds(option.price) : emptyOdds,\n type: PriceType[option.priceType],\n };\n}\n\nexport function optionPriceToBetslipPrice(market: OptionMarket, price: Price): IPrice {\n const option = market.options?.filter((o) => o.price.id === price.id)[0];\n\n return {\n id: price.id,\n isVisible: true,\n marketId: market.id,\n nativeOdds: OddsOperations.createOdds(price),\n type: option?.participantPriceType ? PriceType[option.participantPriceType] : PriceType.Fixed,\n };\n}\n","import { ParticipantMarketStatus, ParticipantOption, ParticipantPriceStatus, ParticipantPriceType, ParticipantStatus } from '@cds/betting-offer';\nimport { OddsOperations, emptyOdds } from '@frontend/sports/odds/feature';\n\nimport { participantOptionPriceToBetslipPrice } from '../price';\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2Pick } from './betslip-v2-pick';\nimport { V2ParticipantPickId } from './pick-id';\nimport { IBetslipV2ParticipantPickStorage, IBetslipV2PickParticipant, IBetslipV2PickParticipantMarket, IPrice } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\n/**\n * General Participant pick\n */\nexport abstract class BetslipV2ParticipantPick extends BetslipV2Pick {\n override id: V2ParticipantPickId;\n override market: IBetslipV2PickParticipantMarket;\n participants: IBetslipV2PickParticipant[];\n betType: string;\n betTypeAltTranslation: string;\n\n static override isPick(pick: BetslipPick): pick is BetslipV2ParticipantPick {\n return pick instanceof BetslipV2ParticipantPick;\n }\n\n protected constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2ParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.market = value.market;\n this.participants = value.participants.map((p) => ({\n ...p,\n prices: p.prices.map((pr) => ({ ...pr })),\n }));\n this.betType = value.betType;\n this.betTypeAltTranslation = value.betTypeAltTranslation;\n }\n\n protected updateFromPrice(thisPrice: IPrice, fixtureOption?: ParticipantOption): void {\n if (!fixtureOption) {\n this.priceHistory.push({ ...thisPrice });\n thisPrice.isVisible = false;\n } else {\n const newPrice = {\n isVisible:\n fixtureOption.marketStatus === ParticipantMarketStatus.Visible &&\n fixtureOption.participantPriceStatus === ParticipantPriceStatus.Visible,\n nativeOdds: emptyOdds,\n };\n if (fixtureOption.priceType === ParticipantPriceType.Fixed) {\n // Set odds only when priceType is fixed.\n newPrice.nativeOdds = OddsOperations.createOdds(fixtureOption.price);\n newPrice.isVisible = newPrice.isVisible && OddsOperations.isOddsValid(newPrice.nativeOdds);\n }\n if (!OddsOperations.equal(newPrice.nativeOdds, thisPrice.nativeOdds) || thisPrice.isVisible !== newPrice.isVisible) {\n this.priceHistory.push({ ...thisPrice });\n }\n thisPrice.isVisible = newPrice.isVisible;\n thisPrice.nativeOdds = newPrice.nativeOdds;\n }\n }\n\n protected addPriceFromOption(participant: IBetslipV2PickParticipant, option: ParticipantOption): void {\n const price = participantOptionPriceToBetslipPrice(option);\n participant.prices.push(price);\n }\n\n protected updateFromParticipantOptions(options: ParticipantOption[]): void {\n const currentMarketOptions = options.filter((o) => o.marketType === this.market.marketType);\n this.market.isVisible =\n !!currentMarketOptions.length && currentMarketOptions.every((o) => o.marketStatus === ParticipantMarketStatus.Visible);\n }\n\n override isPriceVisible(): boolean {\n return !this.participants.some((p) => p.status === ParticipantStatus.Hidden) && !!this.currentPrice && this.currentPrice.isVisible;\n }\n\n override isMarketVisible(): boolean {\n return this.market.isVisible;\n }\n\n /*\tupdateFromMessage(message: MessageEnvelope): void {\n\t\tif (\n\t\t\tisParticipantUpdateEnvelope(message) &&\n\t\t\tmessage.payload.fixtureId === this.fixture.fixtureId &&\n\t\t\tthis.participants.some(p => p.fixtureParticipantId === message.payload.participant.id)\n\t\t) {\n\t\t\tconst command = message.payload;\n\t\t\tconst pickParticipant = this.participants.find(p => p.fixtureParticipantId === message.payload.participant.id)!;\n\t\t\tthis.updateFromParticipant(pickParticipant, command.participant);\n\t\t}\n\t}*/\n\n get isLive(): boolean {\n return false;\n }\n\n override toJSON(): IBetslipV2ParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n id: this.id.toString(),\n market: {\n id: this.market.id,\n isVisible: this.market.isVisible,\n marketType: this.market.marketType,\n isBetBuilderEnabled: false,\n },\n participants: this.participants.map((p) => ({\n ...p,\n })),\n betType: this.betType,\n betTypeAltTranslation: this.betTypeAltTranslation,\n };\n }\n\n get optionName(): SignedName {\n const optionsName = this.participants\n .sort((a, b) => a.position - b.position)\n .map((p) => p.name.name)\n .join(', ');\n\n return {\n name: optionsName,\n signature: '',\n };\n }\n\n get prices(): IPrice[] {\n return this.participants.flatMap((p) => p.prices.filter((pr) => pr.marketId === this.market.id));\n }\n\n abstract override copy(): BetslipV2ParticipantPick;\n}\n","import { ParticipantMarketStatus, ParticipantOption, ParticipantPriceType, ParticipantType } from '@cds/betting-offer';\n\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV2ParticipantPick } from './betslip-v2-participant-pick';\nimport { V2WinPickId } from './pick-id';\nimport { IBetslipV2PickParticipant, IBetslipV2PickWinParticipantMarket, IBetslipV2WinParticipantPickStorage, IPrice, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\n/**\n * General Win market pick\n */\nexport abstract class BetslipV2WinParticipantPick extends BetslipV2ParticipantPick {\n override id: V2WinPickId;\n override market: IBetslipV2PickWinParticipantMarket;\n\n static override isPick(pick: BetslipPick): pick is BetslipV2WinParticipantPick {\n return pick instanceof BetslipV2WinParticipantPick;\n }\n\n protected constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2WinParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.market = value.market;\n }\n\n protected override updateFromParticipantOptions(markets: ParticipantOption[]): void {\n super.updateFromParticipantOptions(markets);\n if (markets.length) {\n const fixedMarket = markets.find((m) => m.priceType === ParticipantPriceType.Fixed);\n const startingPriceMarket = markets.find((m) => m.priceType === ParticipantPriceType.StartingPrice);\n this.market.isStartingPriceAvailable = !!(startingPriceMarket && startingPriceMarket.marketStatus === ParticipantMarketStatus.Visible);\n this.market.isVisible = !!(fixedMarket && fixedMarket.marketStatus === ParticipantMarketStatus.Visible);\n this.market.isEachWay = fixedMarket?.properties.isEachWay || false;\n this.market.placeTerms = fixedMarket?.properties.placeTerms || null;\n }\n }\n\n override isMarketVisible(): boolean {\n if (!this.currentPrice) {\n return false;\n }\n switch (this.currentPrice.type) {\n case PriceType.Fixed:\n return this.market.isVisible;\n case PriceType.StartingPrice:\n return this.market.isStartingPriceAvailable;\n default:\n // only regular price ans starting price for win market\n return false;\n }\n }\n\n isStartingPriceAvailable(): boolean {\n return this.market.isStartingPriceAvailable && this.prices.some((pr) => pr.type === PriceType.StartingPrice && pr.isVisible);\n }\n\n isFixedPriceAvailable(): boolean {\n return this.market.isVisible && this.prices.some((pr) => pr.type === PriceType.Fixed && pr.isVisible);\n }\n\n override toJSON(): IBetslipV2WinParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n market: {\n id: this.market.id,\n isVisible: this.market.isVisible,\n isStartingPriceAvailable: this.market.isStartingPriceAvailable,\n isEachWay: this.market.isEachWay,\n marketType: this.market.marketType,\n placeTerms: this.market.placeTerms && { ...this.market.placeTerms },\n isBetBuilderEnabled: false,\n },\n };\n }\n\n get participant(): IBetslipV2PickParticipant {\n return this.participants[0];\n }\n\n get isEachWay(): boolean {\n return this.market.isEachWay && this.participant.type !== ParticipantType.Favourite;\n }\n\n override get optionName(): SignedName {\n return this.participant.name;\n }\n\n override get prices(): IPrice[] {\n // Small optimization over participants pick, not to use flatArray. Here we know that there is only one participant\n return this.participant.prices.filter((pr) => pr.marketId === this.market.id);\n }\n\n override get currentPrice(): IPrice | undefined {\n return this.prices.find((p) => p.type === this.priceType);\n }\n\n abstract override copy(): BetslipV2WinParticipantPick;\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\n\nimport { BetslipPick } from '../betslip-pick';\nimport { BetslipV2WinParticipantPick } from '../betslip-v2-win-participant-pick';\nimport { GolfPick, IBetslipV2GolfPickFixture, IBetslipV2GolfWinParticipantPickStorage, IBetslipV2PickFixture, PickSubType } from '../pick-models';\nimport { SignedName } from '../signed-name.model';\n\nexport class BetslipV2GolfWinParticipantPick extends BetslipV2WinParticipantPick implements GolfPick {\n override fixture: IBetslipV2GolfPickFixture;\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2GolfWinParticipantPick {\n return pick instanceof BetslipV2GolfWinParticipantPick;\n }\n\n constructor() {\n super();\n }\n\n static fromJSON(value: IBetslipV2GolfWinParticipantPickStorage): BetslipV2GolfWinParticipantPick {\n const pick = new BetslipV2GolfWinParticipantPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2GolfWinParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.fixture = {\n ...(this.fixture as IBetslipV2PickFixture),\n fixtureGroup: value.fixture.fixtureGroup,\n };\n }\n\n override toJSON(): IBetslipV2GolfWinParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n fixture: {\n ...base.fixture,\n fixtureGroup: {\n id: this.fixture.fixtureGroup.id,\n name: {\n name: this.fixture.fixtureGroup.name.name,\n signature: this.fixture.fixtureGroup.name.signature,\n },\n },\n },\n pickSubType: PickSubType.GolfWinPick,\n };\n }\n\n copy(): BetslipV2GolfWinParticipantPick {\n const storage = this.toJSON();\n\n return BetslipV2GolfWinParticipantPick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return this.fixture.fixtureGroup.name;\n }\n\n get marketName(): SignedName {\n return this.fixture.name;\n }\n}\n","import { FixtureStage, FixtureType, FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { cloneDeep } from 'lodash-es';\n\nimport { BetslipPick } from '../betslip-pick';\nimport { BetslipV2OptionMarketPick } from '../betslip-v2-option-market-pick';\nimport { BetslipV2ParticipantPick } from '../betslip-v2-participant-pick';\nimport { BetslipV2Pick } from '../betslip-v2-pick';\nimport { BetslipV2WinParticipantPick } from '../betslip-v2-win-participant-pick';\nimport { V2OptionMarketXCastPickId } from '../pick-id';\nimport {\n HorseRaceParticipantPick,\n HorseRacePick,\n IBetslipV2HorseRaceOptionMarketPickStorage,\n IBetslipV2HorseRaceOptionMarketXCastPickStorage,\n IBetslipV2HorseRaceParticipantPickStorage,\n IBetslipV2HorseRacePickFixture,\n IBetslipV2HorseRacePickFixtureStorage,\n IBetslipV2HorseRacePickStorage,\n IBetslipV2HorseRaceWinParticipantPickStorage,\n IBetslipV2Option,\n IBetslipV2OptionMarket,\n IBetslipV2OptionMarketPickStorage,\n IBetslipV2ParticipantPickStorage,\n IBetslipV2PickFixture,\n IBetslipV2PickFixtureStorage,\n IBetslipV2PickParticipant,\n IPrice,\n ParticipantPickType,\n PickSubType,\n PriceType,\n} from '../pick-models';\nimport { SignedName } from '../signed-name.model';\n\nexport class RacePickProvider {\n static toJSON(pick: HorseRacePick, baseStorage: IBetslipV2PickFixtureStorage): { fixture: IBetslipV2HorseRacePickFixtureStorage } {\n return {\n fixture: {\n ...baseStorage,\n bestOddsGuarantee: pick.fixture.bestOddsGuarantee,\n isRaceOff: pick.fixture.isRaceOff,\n },\n };\n }\n\n static fromJSON(pick: HorseRacePick, fixture: IBetslipV2PickFixture, storage: { fixture: IBetslipV2HorseRacePickFixtureStorage }): void {\n pick.fixture = {\n ...fixture,\n bestOddsGuarantee: storage.fixture.bestOddsGuarantee,\n isRaceOff: storage.fixture.isRaceOff,\n };\n }\n\n static eventName(pick: HorseRacePick): SignedName {\n if (pick.fixture.fixtureType === FixtureType.DayOfRace) {\n return pick.leagueName;\n }\n\n return pick.fixture.name;\n }\n}\n\n/**\n * Horse racing Win participant pick\n */\nexport class BetslipV2HorseRaceWinParticipantPick extends BetslipV2WinParticipantPick implements HorseRaceParticipantPick {\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n private _marketName: string;\n private _marketNameAltTranslation?: string;\n\n override fixture: IBetslipV2HorseRacePickFixture;\n override participants: IBetslipV2PickParticipant[];\n\n static fromJSON(value: IBetslipV2HorseRaceWinParticipantPickStorage): BetslipV2HorseRaceWinParticipantPick {\n const pick = new BetslipV2HorseRaceWinParticipantPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2HorseRaceWinParticipantPick {\n return pick instanceof BetslipV2HorseRaceWinParticipantPick;\n }\n\n constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceWinParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.fromJSON(this, value);\n }\n\n setMarketName(value: string, altTranslation?: string): void {\n this._marketName = value;\n if (altTranslation) {\n this._marketNameAltTranslation = altTranslation;\n }\n }\n\n override toJSON(): IBetslipV2HorseRaceWinParticipantPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n ...this.toParticipantsJSON(this),\n pickSubType: PickSubType.HorseWinPick,\n };\n }\n\n private fromJSON(\n pick: HorseRaceParticipantPick,\n json: { participants: IBetslipV2PickParticipant[]; _marketName: string; _marketNameAltTranslation?: string },\n ): void {\n this._marketName = json._marketName;\n pick.participants = json.participants.map((p) => ({\n ...p,\n prices: p.prices,\n }));\n\n if (json._marketNameAltTranslation) {\n this._marketNameAltTranslation = json._marketNameAltTranslation;\n }\n }\n\n private toParticipantsJSON(pick: HorseRaceParticipantPick): {\n participants: IBetslipV2PickParticipant[];\n _marketName: string;\n _marketNameAltTranslation?: string;\n } {\n return {\n participants: pick.participants.map((p) => ({\n ...p,\n prices: p.prices,\n })),\n _marketName: this._marketName,\n ...(this._marketNameAltTranslation && { _marketNameAltTranslation: this._marketNameAltTranslation }),\n };\n }\n\n override setMarketInvisible(): void {\n super.setMarketInvisible();\n this.market.isStartingPriceAvailable = false;\n }\n\n copy(): BetslipV2HorseRaceWinParticipantPick {\n const storage = this.toJSON();\n\n return BetslipV2HorseRaceWinParticipantPick.fromJSON(storage);\n }\n\n override get participant(): IBetslipV2PickParticipant {\n return this.participants[0];\n }\n\n override get eventName(): SignedName {\n return RacePickProvider.eventName(this);\n }\n\n get marketName(): SignedName {\n return {\n name: this._marketName,\n signature: '',\n nameAlternate: this._marketNameAltTranslation,\n };\n }\n}\n\n/**\n * Horse racing XCast (Forecast, Tricast) pick\n */\nexport class BetslipV2HorseRaceXCastPick extends BetslipV2ParticipantPick implements HorseRaceParticipantPick {\n private _marketName: string;\n private _marketNameAltTranslation?: string;\n override fixture: IBetslipV2HorseRacePickFixture;\n override participants: IBetslipV2PickParticipant[];\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2HorseRaceXCastPick {\n return pick instanceof BetslipV2HorseRaceXCastPick;\n }\n\n static fromJSON(value: IBetslipV2HorseRaceParticipantPickStorage): BetslipV2HorseRaceXCastPick {\n const pick = new BetslipV2HorseRaceXCastPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n constructor() {\n super();\n this.priceType = PriceType.NoPrice;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceParticipantPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.fromJSON(this, value);\n }\n\n private getPermutations(pos: number): number {\n let res = 1;\n for (let i = this.participants.length; i > this.participants.length - pos; i--) {\n res *= i;\n }\n\n return res;\n }\n\n override betCount(): number {\n switch (this.id.pickType) {\n case ParticipantPickType.CombinationForecast:\n return this.getPermutations(2);\n case ParticipantPickType.CombinationTricast:\n return this.getPermutations(3);\n case ParticipantPickType.Forecast:\n case ParticipantPickType.Tricast:\n return 1;\n default:\n return 1;\n }\n }\n\n setMarketName(value: string, altTranslation?: string): void {\n this._marketName = value;\n if (altTranslation) {\n this._marketNameAltTranslation = altTranslation;\n }\n }\n\n override toJSON(): IBetslipV2HorseRaceParticipantPickStorage {\n const base = super.toJSON() as IBetslipV2ParticipantPickStorage & IBetslipV2HorseRacePickStorage;\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n ...this.toParticipantsJSON(this),\n pickSubType: PickSubType.HorseXCastPick,\n };\n }\n\n copy(): BetslipV2HorseRaceXCastPick {\n const storage = this.toJSON();\n\n return BetslipV2HorseRaceXCastPick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return RacePickProvider.eventName(this);\n }\n\n get marketName(): SignedName {\n return {\n name: this._marketName,\n signature: '',\n nameAlternate: this._marketNameAltTranslation,\n };\n }\n\n private fromJSON(\n pick: HorseRaceParticipantPick,\n json: { participants: IBetslipV2PickParticipant[]; _marketName: string; _marketNameAltTranslation?: string },\n ): void {\n this._marketName = json._marketName;\n pick.participants = json.participants.map((p) => ({\n ...p,\n prices: p.prices,\n }));\n\n if (json._marketNameAltTranslation) {\n this._marketNameAltTranslation = json._marketNameAltTranslation;\n }\n }\n\n private toParticipantsJSON(pick: HorseRaceParticipantPick): {\n participants: IBetslipV2PickParticipant[];\n _marketName: string;\n _marketNameAltTranslation?: string;\n } {\n return {\n participants: pick.participants.map((p) => ({\n ...p,\n prices: p.prices,\n })),\n _marketName: this._marketName,\n ...(this._marketNameAltTranslation && { _marketNameAltTranslation: this._marketNameAltTranslation }),\n };\n }\n}\n\n/**\n * Horse racing Option Market Pick\n */\nexport class BetslipV2HorseRaceOptionMarketPick extends BetslipV2OptionMarketPick implements HorseRacePick {\n override fixture: IBetslipV2HorseRacePickFixture;\n betType: string;\n betTypeAltTranslation: string;\n\n static fromJSON(value: IBetslipV2HorseRaceOptionMarketPickStorage): BetslipV2HorseRaceOptionMarketPick {\n const pick = new BetslipV2HorseRaceOptionMarketPick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n constructor() {\n super();\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceOptionMarketPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.betType = value.betType;\n this.betTypeAltTranslation = value.betTypeAltTranslation;\n }\n\n override toJSON(): IBetslipV2HorseRaceOptionMarketPickStorage {\n const base = super.toJSON() as IBetslipV2OptionMarketPickStorage & IBetslipV2HorseRacePickStorage;\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n pickSubType: PickSubType.HorseOptionMarketPick,\n betType: this.betType,\n betTypeAltTranslation: this.betTypeAltTranslation,\n };\n }\n\n copy(): BetslipV2HorseRaceOptionMarketPick {\n const storage = this.toJSON();\n\n return BetslipV2HorseRaceOptionMarketPick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return this.leagueName;\n }\n\n override get competitionName(): SignedName {\n return this.leagueName;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2HorseRaceOptionMarketPick {\n return pick instanceof BetslipV2HorseRaceOptionMarketPick;\n }\n\n isStartingPriceAvailable(): boolean {\n return this.market.isStartingPriceAvailable!;\n }\n\n isFixedPriceAvailable(): boolean {\n return this.market.isVisible && this.market.isFixedPriceAvailable!;\n }\n}\n\nexport class BetslipV2OptionMarketXCastRacePick extends BetslipV2Pick {\n options: IBetslipV2Option[];\n override market: IBetslipV2OptionMarket;\n override id: V2OptionMarketXCastPickId;\n override fixture: IBetslipV2HorseRacePickFixture;\n betType: string;\n betTypeAltTranslation: string;\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n get marketName(): SignedName {\n return this.market.name;\n }\n\n get isLive(): boolean {\n return this.fixture.stage === FixtureStage.Live;\n }\n\n get optionName(): SignedName {\n const optionsName = this.options\n .sort((a, b) => a.position! - b.position!)\n .map((p) => p.name.name)\n .join(', ');\n\n return {\n name: optionsName,\n signature: '',\n };\n }\n\n private getPermutations(pos: number): number {\n let res = 1;\n for (let i = this.options.length; i > this.options.length - pos; i--) {\n res *= i;\n }\n\n return res;\n }\n\n override betCount(): number {\n switch (this.id.pickType) {\n case ParticipantPickType.CombinationForecast:\n return this.getPermutations(2);\n case ParticipantPickType.CombinationTricast:\n return this.getPermutations(3);\n case ParticipantPickType.Forecast:\n case ParticipantPickType.Tricast:\n return 1;\n default:\n return 1;\n }\n }\n\n get selectedOptions(): IBetslipV2Option[] {\n return this.options;\n }\n\n static override isPick(pick: BetslipPick): pick is BetslipV2OptionMarketXCastRacePick {\n return pick instanceof BetslipV2OptionMarketXCastRacePick;\n }\n\n static fromJSON(value: IBetslipV2HorseRaceOptionMarketXCastPickStorage): BetslipV2OptionMarketXCastRacePick {\n const pick = new BetslipV2OptionMarketXCastRacePick();\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n constructor() {\n super();\n this.priceType = PriceType.NoPrice;\n }\n\n protected override initPropertiesFromJSON(value: IBetslipV2HorseRaceOptionMarketXCastPickStorage): void {\n super.initPropertiesFromJSON(value);\n RacePickProvider.fromJSON(this, this.fixture, value);\n this.market = value.market;\n this.betType = value.betType;\n this.betTypeAltTranslation = value.betTypeAltTranslation;\n\n this.options = value.options.map((o) => ({\n ...o,\n prices: cloneDeep(o.prices),\n }));\n }\n\n override toJSON(): IBetslipV2HorseRaceOptionMarketXCastPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n ...RacePickProvider.toJSON(this, base.fixture),\n pickSubType: PickSubType.HorseOptionMarketXCastRacePick,\n options: this.options,\n market: this.market,\n betType: this.betType,\n betTypeAltTranslation: this.betTypeAltTranslation,\n };\n }\n\n copy(): BetslipV2OptionMarketXCastRacePick {\n const storage = this.toJSON();\n\n return BetslipV2OptionMarketXCastRacePick.fromJSON(storage);\n }\n\n override get eventName(): SignedName {\n return this.leagueName;\n }\n\n override get competitionName(): SignedName {\n return this.leagueName;\n }\n\n get prices(): IPrice[] {\n return this.options[0].prices.filter((pr) => pr.marketId === this.market.id);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { BetslipBetBuilderPick } from '../../../core/picks/betslip-bet-builder-pick';\nimport { BetslipGroupPick } from '../../../core/picks/betslip-group-pick';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetslipV1Pick } from '../../../core/picks/betslip-v1-pick';\nimport { BetslipV2StandardPick } from '../../../core/picks/betslip-v2-standard-pick';\nimport {\n BetslipGroupPickStorage,\n IBetslipBetBuilderPickStorage,\n IBetslipPickStorage,\n IBetslipV1PickStorage,\n IBetslipV2GolfWinParticipantPickStorage,\n IBetslipV2HorseRaceOptionMarketPickStorage,\n IBetslipV2HorseRaceOptionMarketXCastPickStorage,\n IBetslipV2HorseRaceParticipantPickStorage,\n IBetslipV2HorseRaceWinParticipantPickStorage,\n IBetslipV2OptionMarketPickStorage,\n PickSubType,\n} from '../../../core/picks/pick-models';\nimport { BetslipV2GolfWinParticipantPick } from '../../../core/picks/sport-specific/betslip-v2-golf-picks';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n BetslipV2OptionMarketXCastRacePick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\n\nexport function loadPickFromJSON(value: IBetslipPickStorage) {\n switch (value.pickSubType) {\n case PickSubType.V1Pick: {\n return BetslipV1Pick.fromJSON(value as IBetslipV1PickStorage);\n }\n case PickSubType.HorseWinPick: {\n return BetslipV2HorseRaceWinParticipantPick.fromJSON(value as IBetslipV2HorseRaceWinParticipantPickStorage);\n }\n case PickSubType.HorseXCastPick: {\n return BetslipV2HorseRaceXCastPick.fromJSON(value as IBetslipV2HorseRaceParticipantPickStorage);\n }\n case PickSubType.HorseOptionMarketPick: {\n return BetslipV2HorseRaceOptionMarketPick.fromJSON(value as IBetslipV2HorseRaceOptionMarketPickStorage);\n }\n case PickSubType.GolfWinPick: {\n return BetslipV2GolfWinParticipantPick.fromJSON(value as IBetslipV2GolfWinParticipantPickStorage);\n }\n case PickSubType.BetBuilderPick: {\n return BetslipBetBuilderPick.fromJSON(value as IBetslipBetBuilderPickStorage);\n }\n case PickSubType.StandardV2Pick: {\n return BetslipV2StandardPick.fromJSON(value as IBetslipV2OptionMarketPickStorage);\n }\n case PickSubType.GroupedPicks: {\n return BetslipGroupPick.fromJSON(value as BetslipGroupPickStorage);\n }\n case PickSubType.HorseOptionMarketXCastRacePick: {\n return BetslipV2OptionMarketXCastRacePick.fromJSON(value as IBetslipV2HorseRaceOptionMarketXCastPickStorage);\n }\n\n default: {\n throw new Error('Unknown pick sub type');\n }\n }\n}\n@Injectable({ providedIn: 'root' })\nexport class BetslipPickStorageLoader {\n loadPick(value: IBetslipPickStorage): BetslipPick {\n return loadPickFromJSON(value);\n }\n}\n","import { BasePrice } from '@cds/betting-offer';\nimport { LegInfo, ResultState, SuspensionState } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { OddsOperations, emptyOdds } from '@frontend/sports/odds/feature';\n\nimport { BetslipPick } from './picks/betslip-pick';\nimport { BetslipV1Pick } from './picks/betslip-v1-pick';\nimport { BetslipV2OptionMarketPick } from './picks/betslip-v2-option-market-pick';\nimport { IPrice, PriceType } from './picks/pick-models';\n\nexport const mapLegInfoToClientLegInfo = (legInformation: LegInfo[]): { clientLegId: string; isVisible: boolean; isClosed: boolean }[] => {\n return legInformation?.map((legInfo) => mapLegVisibility(legInfo)) ?? [];\n};\n\nexport const mapLegVisibility = (legInformation: LegInfo): { clientLegId: string; isVisible: boolean; isClosed: boolean } => ({\n isVisible: !isSuspended(legInformation.suspensionState),\n isClosed: legInformation.resultState !== ResultState.Open,\n clientLegId: legInformation.clientLegId,\n});\n\nexport const isSuspended = (suspensionState?: SuspensionState): boolean => suspensionState !== SuspensionState.MarketUnsuspended;\n\nexport const buildIPriceFromBasePrice = (price: BasePrice): IPrice => {\n try {\n return {\n nativeOdds: OddsOperations.createOdds(price),\n type: PriceType.Fixed,\n id: 1,\n isVisible: true,\n marketId: 1,\n };\n } catch {\n return buildIPriceFromBasePrice(emptyPrice());\n }\n};\n\nexport const emptyPrice = (): BasePrice => {\n return {\n americanOdds: emptyOdds.moneyline,\n odds: emptyOdds.decimals,\n denominator: emptyOdds.fractional.denominator,\n numerator: emptyOdds.fractional.numerator,\n };\n};\n\nexport const emptyIPrice = buildIPriceFromBasePrice(emptyPrice());\n\nexport const resolveClientLegId = (leg: BetslipPick) => {\n if (BetslipV1Pick.isPick(leg)) {\n return `1:${leg.event.id}:${leg.market.id}:${leg.optionId}`;\n }\n if (BetslipV2OptionMarketPick.isPick(leg)) {\n return `${leg.fixture.fixtureId}:${leg.market.id}:${leg.optionId}`;\n }\n\n return '';\n};\n","export interface GroupLegState {\n clientLegId: string;\n isNoncombinable: boolean;\n isClosed: boolean;\n isVisible: boolean;\n}\n\nexport function defaultLegState(): GroupLegState {\n return {\n clientLegId: '',\n isNoncombinable: false,\n isClosed: false,\n isVisible: true,\n };\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\n\nimport { loadPickFromJSON } from '../../modules/picks/services/betslip-pick-storage-loader';\nimport { emptyIPrice, resolveClientLegId } from '../group-pick-helpers';\nimport { BetslipGroupInformation } from '../groups/betslip-group-information';\nimport { BetslipPick } from './betslip-pick';\nimport { BetslipV1Pick } from './betslip-v1-pick';\nimport { BetslipV2Pick } from './betslip-v2-pick';\nimport { GroupLegState, defaultLegState } from './group-leg-state';\nimport { GroupPickId, PickId } from './pick-id';\nimport { BetslipGroupPickStorage, IPrice, PickOddsState, PickSubType, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\ninterface GroupPickInputs {\n sgpId: string;\n price: IPrice | null;\n legsInfo: { clientLegId: string; isVisible: boolean; isClosed: boolean }[];\n isSuspended: boolean;\n}\n\nexport abstract class BaseBetslipGroupPick extends BetslipPick {\n override id: GroupPickId;\n protected legs: BetslipPick[];\n //unique value provided for the group combination by the provider that can be used by clients for subscribing to price updates\n protected _sgpId: string | null;\n protected _legsState: Record = {};\n protected price: IPrice;\n isSuspended: boolean; // the whole combination can be suspended\n protected hasEmptyPrice: boolean;\n\n override get groupInfo(): BetslipGroupInformation {\n return this._groupInfo!;\n }\n\n static isPick(pick: BetslipPick): pick is BaseBetslipGroupPick {\n return pick instanceof BaseBetslipGroupPick;\n }\n\n get sgpId(): string | null {\n return this._sgpId;\n }\n\n getLegs(): BetslipPick[] {\n return this.legs;\n }\n\n get legsState(): Record {\n return this._legsState;\n }\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return this.legs[0].eventParticipants;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return this.legs[0].eventViewType;\n }\n\n hasPick(id: PickId): boolean {\n return this.legs.some((p) => p.id.toString() === id.toString());\n }\n\n legIsNoncombinable(pickId: PickId): boolean {\n const { isNoncombinable } = this._legsState[pickId.toString()];\n\n return isNoncombinable;\n }\n\n getOddsStateForLegId(legId: PickId): PickOddsState {\n const leg = this.legs.find((l) => l.id === legId);\n\n if (!leg) return PickOddsState.Closed;\n\n return this.getOddsStateForLeg(leg);\n }\n\n private getOddsStateForLeg(leg: BetslipPick): PickOddsState {\n const legOddsState = leg.oddsState;\n const { isClosed, isVisible } = this._legsState[leg.id.toString()];\n\n if (isClosed || legOddsState === PickOddsState.Closed) {\n return PickOddsState.Closed;\n }\n\n if (!isVisible || legOddsState === PickOddsState.Locked) {\n return PickOddsState.Locked;\n }\n\n return PickOddsState.Open;\n }\n\n protected hasNonOpenLegs(): boolean {\n return this.legs.some((leg) => this.getOddsStateForLeg(leg) !== PickOddsState.Open);\n }\n\n groupHasUncombinableLeg(): boolean {\n return Object.values(this._legsState).some((t) => t.isNoncombinable);\n }\n\n protected isPriceVisible(): boolean {\n return true;\n }\n\n isMarketVisible(): boolean {\n return true;\n }\n\n get isLive(): boolean {\n return this.legs.some((leg) => leg.isLive);\n }\n\n get prices(): IPrice[] {\n return [this.price];\n }\n\n //Overriding this because we want to show an invalid price in case there are any nonOpen legs in a group.\n //The price returned from the price call can still be a valid one.\n override get currentPrice(): IPrice | undefined {\n return this.hasEmptyPrice ? emptyIPrice : this.prices[0];\n }\n\n //We still want to run our price validators on the 'actual' price\n get actualGroupPrice(): IPrice | undefined {\n return this.prices[0];\n }\n\n get eventName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.eventName;\n }\n\n get marketName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n throw new Error(`Grouped picks market name getter shouldn't be invoked!`);\n }\n\n get optionName(): SignedName {\n throw new Error(`Grouped picks option name getter shouldn't be invoked!`);\n }\n\n get sportId(): SportConstant {\n return this.legs[0]!.sportId;\n }\n\n //Commented this particular code as AngstromSGP is not PROD-ready, It will be enabled,So GroupPick will not be initiated\n // get partitionId(): number | null {\n // //TO CHECK: whether all picks within the group belong to the same event, so it's a safe assumption\n // const fixtureId = this.tryGetFixtureId;\n // if (this.tryGetFixtureId === '') {\n // return null;\n // } else {\n // return getPartition(fixtureId);\n // }\n // }\n\n get partitionId(): number | undefined {\n throw new Error('Method not implemented.');\n }\n\n get isVirtual(): boolean {\n return this.legs.reduce((acc, curr) => acc && curr.isVirtual, true);\n }\n\n get eventDate(): Date {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.eventDate;\n }\n\n get regionName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.regionName;\n }\n\n get competitionName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.competitionName;\n }\n\n get competitionId(): number {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.competitionId;\n }\n\n get sportName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.sportName;\n }\n\n get leagueName(): SignedName {\n //all picks within the group belong to the same event, so it's a safe assumption\n return this.legs[0]!.leagueName;\n }\n\n get tryGetEventId(): string {\n const v1PickWithinTheGroup = this.legs.find((p) => p instanceof BetslipV1Pick);\n if (v1PickWithinTheGroup) {\n return (v1PickWithinTheGroup as BetslipV1Pick).event.id;\n }\n\n return '';\n }\n\n get tryGetFixtureId(): string {\n const v2PickWithinTheGroup = this.legs.find((p) => p instanceof BetslipV2Pick);\n\n if (v2PickWithinTheGroup) {\n return (v2PickWithinTheGroup as BetslipV2Pick).fixture.fixtureId;\n }\n\n return '';\n }\n\n get eventId(): string {\n return this.tryGetEventId || this.tryGetFixtureId;\n }\n\n protected override initPropertiesFromJSON(value: BetslipGroupPickStorage): void {\n super.initPropertiesFromJSON(value);\n this.price = value.price;\n this._sgpId = value.SGPId;\n this._legsState = value.legsState;\n }\n\n protected initLegState(legInfos: { clientLegId: string; isVisible: boolean; isClosed: boolean }[]) {\n this._legsState = this.legs.reduce>((acc, leg) => {\n const clientLegId = resolveClientLegId(leg);\n const legInfo = legInfos.find((li) => li.clientLegId === clientLegId) ?? defaultLegState();\n acc[leg.id.toString()] = {\n clientLegId, // in case the defaultLegState was used we don't have a clientLegId\n isVisible: legInfo.isVisible,\n isClosed: legInfo.isClosed,\n isNoncombinable: !leg.market.isBetBuilderEnabled,\n };\n\n return acc;\n }, {});\n }\n\n legExist(id: PickId): boolean {\n return !!this.legs.find((t) => t.id.toString() === id.toString());\n }\n\n abstract addPick(addPickInput: GroupPickInputs & { pick: BetslipPick }): BaseBetslipGroupPick;\n abstract addPicks(addPicksInput: GroupPickInputs & { picks: BetslipPick[] }): BaseBetslipGroupPick;\n abstract removePicks(removePickInput: GroupPickInputs & { ids: PickId[] }): BetslipPick;\n abstract updatePicks(picks: BetslipPick[]): BaseBetslipGroupPick;\n\n setPrice(price: IPrice): void {\n this.price = price;\n }\n\n override isOpen(): boolean {\n return super.isOpen();\n }\n}\n\nexport class BetslipGroupPick extends BaseBetslipGroupPick {\n constructor(input: GroupPickInputs & { groupInfo: BetslipGroupInformation; picks: BetslipPick[] }) {\n super();\n if (!input.groupInfo?.groupId) {\n throw new Error(`Can't init a group with empty group info!`);\n }\n this.priceType = PriceType.Fixed;\n this.id = new GroupPickId(input.groupInfo);\n this.setGroupInfo(input.groupInfo);\n this._sgpId = input.sgpId;\n this.legs = input.picks;\n if (input.price) {\n this.price = input.price;\n }\n this.acceptedPrice = { price: input.price, isManuallyAccepted: true };\n this.initLegState(input.legsInfo);\n this.subscriptionContext = input.sgpId ? [input.sgpId] : [];\n this.isSuspended = input.isSuspended;\n this.hasEmptyPrice = this.hasNonOpenLegs();\n }\n\n addPick(addPickInput: GroupPickInputs & { pick: BetslipPick }): BaseBetslipGroupPick {\n if (!!addPickInput.pick.groupInfo && addPickInput.pick.groupInfo.groupId !== this.groupInfo.groupId) {\n throw new Error(`Adding pick to a group failed, can't add a pick to a group with different groupId`);\n }\n\n if (this.legExist(addPickInput.pick.id)) {\n throw new Error(`Leg already exists in the group`);\n }\n const group = this.toJSON();\n\n const { sgpId, price, isSuspended, legsInfo } = addPickInput;\n\n return new BetslipGroupPick({\n groupInfo: this._groupInfo!,\n sgpId,\n price,\n isSuspended,\n legsInfo,\n picks: [...group.picks.map((t) => loadPickFromJSON(t)), addPickInput.pick],\n });\n }\n\n addPicks(addPicksInput: GroupPickInputs & { picks: BetslipPick[] }): BaseBetslipGroupPick {\n if (!!addPicksInput.picks[0].groupInfo && addPicksInput.picks[0].groupInfo.groupId !== this.groupInfo.groupId) {\n throw new Error(`Adding pick to a group failed, can't add a pick to a group with different group fixture`);\n }\n\n if (addPicksInput.picks.some((pick) => this.legExist(pick.id))) {\n throw new Error(`Leg already exists in the group`);\n }\n const group = this.toJSON();\n\n const { sgpId, price, isSuspended, legsInfo } = addPicksInput;\n\n return new BetslipGroupPick({\n groupInfo: this._groupInfo!,\n picks: [...group.picks.map((t) => loadPickFromJSON(t)), ...addPicksInput.picks],\n sgpId,\n price,\n legsInfo,\n isSuspended,\n });\n }\n\n removePicks(removePicksInput: GroupPickInputs & { ids: PickId[] }): BetslipPick {\n if (!removePicksInput.ids.every((inputPick) => this.legs.find((p) => p.id.toString() === inputPick.toString()))) {\n throw new Error(`Removing picks failed, at least one pick doesn't belong to the specified group`);\n }\n //if only 1 pick remains in the group, break the group down, and return the remaining pick as a single pick\n if (this.legs.length === removePicksInput.ids.length + 1) {\n const remainingLeg = this.legs.find((l) => !removePicksInput.ids.map((pid) => pid.toString()).includes(l.id.toString()));\n\n if (!remainingLeg) {\n throw new Error(`Removing picks failed, remaining leg was not found`);\n }\n\n return remainingLeg;\n }\n\n const group = this.toJSON();\n group.picks = group.picks.filter((p) => !removePicksInput.ids.some((inputPick) => p.id.toString() === inputPick.toString()));\n const { sgpId, price, isSuspended, legsInfo } = removePicksInput;\n\n return new BetslipGroupPick({\n groupInfo: group.groupInfo!,\n picks: group.picks.map((t) => loadPickFromJSON(t)),\n sgpId,\n price,\n legsInfo,\n isSuspended,\n });\n }\n\n updatePicks(picks: BetslipPick[]): BaseBetslipGroupPick {\n return new BetslipGroupPick({\n groupInfo: this._groupInfo!,\n sgpId: this._sgpId!,\n picks,\n price: this.price,\n legsInfo: Object.values(this._legsState),\n isSuspended: this.isSuspended,\n });\n }\n\n copy(): BetslipGroupPick {\n const copy = this.toJSON();\n\n return BetslipGroupPick.fromJSON(copy);\n }\n\n static fromJSON(value: BetslipGroupPickStorage): BetslipGroupPick {\n if (!value.groupInfo) {\n throw new Error(`Failed instantiating group from storage value, missing group info!`);\n }\n const pick = new BetslipGroupPick({\n groupInfo: value.groupInfo,\n sgpId: value.SGPId,\n picks: value.picks.map((t) => loadPickFromJSON(t)),\n price: value.price,\n legsInfo: Object.values(value.legsState).map((legState) => ({\n clientLegId: legState.clientLegId,\n isVisible: legState.isVisible,\n isClosed: legState.isClosed,\n })),\n isSuspended: value.isSuspended,\n });\n pick.initPropertiesFromJSON(value);\n\n return pick;\n }\n\n override toJSON(): BetslipGroupPickStorage {\n const base = super.toJSON();\n\n return {\n ...base,\n picks: this.legs.map((p) => p.toJSON()),\n price: this.price,\n SGPId: this._sgpId ?? '',\n legsState: this._legsState,\n pickSubType: PickSubType.GroupedPicks,\n isSuspended: this.isSuspended,\n };\n }\n}\n","import { FixtureViewType } from '@cds/betting-offer';\nimport { EventParticipant } from '@frontend/sports/betting-offer/feature/model';\n\nimport { BetslipPick } from './betslip-pick';\nimport { PickId } from './pick-id';\nimport { IPickTracking, IPrice, PriceType } from './pick-models';\nimport { SignedName } from './signed-name.model';\n\nexport class BetslipUnknownPick extends BetslipPick {\n readonly legs: BetslipPick[];\n static isPick(pick: BetslipPick): pick is BetslipUnknownPick {\n return pick instanceof BetslipUnknownPick;\n }\n\n constructor(pickId: PickId, tracking?: IPickTracking, legs?: BetslipPick[]) {\n super();\n this.id = pickId;\n this.market = {\n id: 0,\n isVisible: false,\n isBetBuilderEnabled: false,\n };\n this.priceType = PriceType.NoPrice;\n this.acceptedPrice = {\n price: null,\n isManuallyAccepted: false,\n };\n this.isAvailable = false;\n this.tracking = tracking || { source: '' };\n this.legs = legs ?? [];\n }\n\n // this should not be used, we have the 'legs' field. It exists only because the GroupPicks has a similar function,\n // and it's convenient to combine if statements. IMO GroupPicks should also ditch it and use a readonly field\n getLegs(): BetslipPick[] {\n return this.legs;\n }\n\n override copy(): BetslipUnknownPick {\n const base = super.toJSON();\n\n const pick = new BetslipUnknownPick(PickId.Empty(), void 0, this.legs);\n pick.initPropertiesFromJSON(base);\n\n return pick;\n }\n\n override get eventParticipants(): EventParticipant[] | undefined {\n return undefined;\n }\n\n override get eventViewType(): FixtureViewType | undefined {\n return undefined;\n }\n\n override get eventName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get isLive(): boolean {\n return false;\n }\n\n override isMarketVisible(): boolean {\n return false;\n }\n\n protected override isPriceVisible(): boolean {\n return false;\n }\n\n override get isVirtual(): boolean {\n return false;\n }\n\n override get marketName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get eventDate(): Date {\n return new Date();\n }\n\n override get regionName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get competitionName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get competitionId(): number {\n return 0;\n }\n\n override get leagueName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get optionName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get prices(): IPrice[] {\n return [];\n }\n\n override get sportName(): SignedName {\n return {\n name: '',\n signature: '',\n };\n }\n\n override get partitionId(): number | undefined {\n return undefined;\n }\n\n override get sportId(): number {\n return 0;\n }\n}\n","import { uniqBy } from 'lodash-es';\n\nimport { BetslipBetBuilderPick } from './picks/betslip-bet-builder-pick';\nimport { BaseBetslipGroupPick } from './picks/betslip-group-pick';\nimport { BetslipPick } from './picks/betslip-pick';\nimport { BetslipUnknownPick } from './picks/betslip-unknown-pick';\nimport { BetslipV2OptionMarketPick } from './picks/betslip-v2-option-market-pick';\nimport { BetBuilderPickId, GroupPickId, PickId } from './picks/pick-id';\n\nexport function getPicksCount(picks: BetslipPick[]): number {\n return picks.flatMap((pick) => (BaseBetslipGroupPick.isPick(pick) || isUnknownPickWithLegs(pick) ? pick.getLegs() : pick)).length;\n}\n\nexport function isBetBuilderPick(pick: BetslipPick): pick is BetslipBetBuilderPick | BaseBetslipGroupPick {\n return BetslipBetBuilderPick.isPick(pick) || BaseBetslipGroupPick.isPick(pick);\n}\n\nexport function isBetslipBetBuilderPick(pick: BetslipPick): pick is BetslipBetBuilderPick {\n return BetslipBetBuilderPick.isPick(pick);\n}\n\nexport function isBaseBetslipGroupPick(pick: BetslipPick): pick is BaseBetslipGroupPick {\n return BaseBetslipGroupPick.isPick(pick);\n}\n\nexport function isBetBuilderPickId(pickId: PickId): pickId is BetBuilderPickId | GroupPickId {\n return BetBuilderPickId.isId(pickId) || GroupPickId.isId(pickId);\n}\n\nexport function getOptionNameFromBetslipPick(pick: BetslipPick) {\n return pick.marketName.name + ' - ' + pick.optionName.name;\n}\n\nexport function getLegsCount(picks: BetslipPick[]): number {\n const flattenedPicks = uniqBy(flattenGroupPicksIfAny(picks), (pick) => pick.id.toString());\n\n return flattenedPicks.reduce((acc, curr) => {\n if (BetslipBetBuilderPick.isPick(curr)) {\n return acc + curr.getLegCount();\n }\n\n return acc + 1;\n }, 0);\n}\n\nexport function flattenGroupPicksIfAny(picks: BetslipPick[]): BetslipPick[] {\n return picks.flatMap((p) => (BaseBetslipGroupPick.isPick(p) || isUnknownPickWithLegs(p) ? p.getLegs() : p));\n}\n\n// Expands the legs for group picks. Keeps the groups in the list. Unique check added in case we're calling this method on a list multiple times\nexport function flattenPicks(picks: BetslipPick[]): BetslipPick[] {\n return uniqBy(\n picks.flatMap((p) => (BaseBetslipGroupPick.isPick(p) || isUnknownPickWithLegs(p) ? [p, ...p.getLegs()] : p)),\n (pick) => pick.id.toString(),\n );\n}\n\n// Checks if picks qualify as valid one pick combo. Currently, 2 cases are supported:\n// - Group picks(e.g. Angstrom bet builders)\n// - BetBuilder picks(eg. Sportcast) ONLY in case the feature is enabled\nexport const isValidOnePickCombo = (picks: BetslipPick[], isSportcastAsComboEnabled: boolean) =>\n picks.length === 1 && (BaseBetslipGroupPick.isPick(picks[0]) || (isSportcastAsComboEnabled && BetslipBetBuilderPick.isPick(picks[0])));\n\n// we need the { legs: { 0: BetslipPick } } restriction because if we have only the pick is BetslipUnknownPick\n// then TS will narrow the result type to BetslipUnknownPick, although we expect just a subclass of BetslipUnknownPick\nexport function isUnknownPickWithLegs(pick: BetslipPick): pick is BetslipUnknownPick & { legs: { 0: BetslipPick } } {\n return BetslipUnknownPick.isPick(pick) && pick.legs.length > 0;\n}\n\nexport function getOptionIsDraw(pick: BetslipPick): boolean | undefined {\n return pick instanceof BetslipV2OptionMarketPick ? (pick as BetslipV2OptionMarketPick).option.isDraw : undefined;\n}\n","export enum BetslipType {\n Single = 'SINGLE',\n Combo = 'COMBO',\n Teaser = 'TEASER',\n System = 'SYSTEM',\n EditBet = 'EDIT_MY_BET',\n BetBuilder = 'BET_BUILDER',\n}\n","import { EdsPromoToken, PromoToken } from '@bpos/common/bet-placement';\nimport { Decimal } from 'decimal.js';\n\nimport { BetslipPick } from '../../core/picks/betslip-pick';\nimport { IBasePrice, PickOddsState } from '../../core/picks/pick-models';\nimport { SlipId } from '../../modules/bet-generation/models';\nimport { SlipType } from '../../modules/bet-generation/slip-type';\n\nexport enum OverAskFlowState {\n None, // OverAsk is not active\n Pending, // User is waiting for trader response,\n Review, // User is reviewing trader offer.\n}\n\nexport enum OverAskRejectReason {\n CancelWaiting = 1,\n UserRejectOffer = 2,\n TraderRejectOffer = 3,\n AcceptOfferTimeout = 4,\n BettingOfferNotAvailable = 5,\n PlaceBetFailed = 6,\n TechnicalError = 7,\n ArcUserProfileRestriction = 8,\n}\n\nexport enum OverAskBetslipError {\n OfferExpired = 'ErrorOfferExpired',\n OfferRejected = 'ErrorOfferRejected',\n FlowFailed = 'ErrorFlowFailed',\n PlaceBetFailed = 'ErrorPlaceBetFailed',\n MaximumWinOfBetExceeded = 'ErrorMaximumWinOfBetExceeded',\n}\n\nexport enum OverAskMarketTypeChange {\n Win = 'WIN',\n EachWay = 'EW',\n}\n\nexport interface OverAskPickChanges {\n price?: IBasePrice; // Change in the price\n marketType?: OverAskMarketTypeChange; // Change in market type.\n}\n\nexport interface OverAskBetChange {\n stake?: Decimal; // Change in the bet stake\n}\n\nexport interface OverAskPick {\n index: number;\n pick: BetslipPick;\n isEachWay: boolean;\n isBanker: boolean;\n price: IBasePrice;\n oddsState: PickOddsState;\n changes: OverAskPickChanges;\n}\n\nexport interface OverAskBet {\n slipId: SlipId;\n index: number;\n stake: Decimal;\n type: SlipType;\n isRemoved: boolean;\n picks: OverAskPick[];\n changes: OverAskBetChange;\n promoTokens: PromoToken[];\n edsPromoTokens: EdsPromoToken[];\n betCount?: number;\n}\n\nexport interface IOverAskMessages {\n acceptChanges: string;\n acceptChangesAndPlaceBet: string;\n cancel: string;\n newOdds: string;\n stake: string;\n newStake: string;\n pendingRejectDialogTitle: string;\n pendingBetPlacementStatus: string;\n pendingCheckingYourBet: string;\n reviewInfoMessage: string;\n rejectDialogCancelButton: string;\n pendingRejectDialogContent: string;\n pendingRejectDialogDismissButton: string;\n pendingTitle: string;\n reviewBadgeTitle: string;\n reviewLimitWarning: string;\n reviewInPlayBadgeTitle: string;\n reviewInPlayInfoMessage: string;\n reviewOfferExpires: string;\n reviewRejectDialogTitle: string;\n reviewRejectDialogContent: string;\n reviewRejectDialogDismissButton: string;\n summaryOfferChanges: string;\n removed: string;\n undo: string;\n totalOdds: string;\n totalStake: string;\n possibleWinnings: string;\n possibleWinningsNet: string;\n tax: string;\n maxReturn: string;\n ErrorOfferRejected: string;\n ErrorOfferExpired: string;\n ErrorOfferRejectedTitle: string;\n ErrorOfferExpiredTitle: string;\n ErrorFlowFailed: string;\n ErrorFlowFailedTitle: string;\n ErrorMaximumWinOfBetExceeded: string;\n ErrorOfferZeroStakeHint: string;\n betslipLockedToastMessage: string;\n winMarketAbbr: string;\n eachWayAbbr: string;\n stakeChangeHeader: string;\n stakeChangeMsg: string;\n stakeChangeToggle: string;\n editStakeChangeDesktopUrl: string;\n editStakeChangeMobileUrl: string;\n}\n","/**\n * We should convert picks from instance objects to plain objects and use these helpers instead of methods or properties of the pick.\n * We need to do this, in order to keep plain objects in the betslip state and to apply easily updates and mutate the object as well serializing it.\n */\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { CalculatedOdds, Fraction, OddsConverter, OddsOperations, emptyCalculatedOdds } from '@frontend/sports/odds/feature';\nimport { Decimal } from 'decimal.js';\n\nimport { IBetslipState } from '../../../base/store/state';\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetslipBetBuilderPick } from '../../../core/picks/betslip-bet-builder-pick';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetslipV1Pick } from '../../../core/picks/betslip-v1-pick';\nimport { BetslipV2OptionMarketPick } from '../../../core/picks/betslip-v2-option-market-pick';\nimport { BetslipV2ParticipantPick } from '../../../core/picks/betslip-v2-participant-pick';\nimport { BetslipV2Pick } from '../../../core/picks/betslip-v2-pick';\nimport { BetslipV2StandardPick } from '../../../core/picks/betslip-v2-standard-pick';\nimport { BetslipV2WinParticipantPick } from '../../../core/picks/betslip-v2-win-participant-pick';\nimport { PickId } from '../../../core/picks/pick-id';\nimport {\n HorseRaceParticipantPickType,\n HorseRacePickType,\n IBasePrice,\n IPlaceTerms,\n ParticipantPickType,\n PickOddsState,\n PickType,\n PriceType,\n} from '../../../core/picks/pick-models';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n BetslipV2OptionMarketXCastRacePick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\nimport { OverAskFlowState, OverAskMarketTypeChange } from '../../../model/over-ask/over-ask';\n\n/**\n * Check BetslipV2WinParticipantPick::getEachWayOdds\n *\n * @param price\n * @param placeTerms\n */\nexport function getEachWayOdds(price: IBasePrice, placeTerms: IPlaceTerms): CalculatedOdds {\n if (price.type === PriceType.Fixed && OddsOperations.isOddsValid(price.nativeOdds)) {\n const placeTermsFactor = new Fraction(placeTerms.numerator, placeTerms.denominator);\n\n const euOdds = OddsConverter.decimalToEachWay(new Decimal(price.nativeOdds.decimals), placeTermsFactor);\n const ukOdds = OddsConverter.fractionToEachWay(Fraction.fromJSON(price.nativeOdds.fractional), placeTermsFactor);\n const usOdds = OddsConverter.usToEachWay(new Decimal(price.nativeOdds.moneyline), placeTermsFactor);\n\n return {\n decimals: euOdds,\n fractional: ukOdds,\n moneyline: usOdds,\n };\n } else {\n return emptyCalculatedOdds;\n }\n}\n\nexport const isPickEachWayCapable = function (pick: BetslipPick): pick is BetslipV2WinParticipantPick | BetslipV2OptionMarketPick {\n return (BetslipV2WinParticipantPick.isPick(pick) || BetslipV2OptionMarketPick.isPick(pick)) && pick.isEachWay;\n};\n\nconst getPickEachWayTerms = function (pick: BetslipPick): IPlaceTerms | null {\n if (isPickEachWayCapable(pick)) {\n return pick.market.placeTerms;\n }\n\n return null;\n};\n\nexport function getPickEachWayInfo(pick: BetslipPick): { placeTerms: IPlaceTerms } | null {\n const placeTerms = getPickEachWayTerms(pick);\n\n return placeTerms ? { placeTerms } : null;\n}\n\nexport function getPickPrice(pick: BetslipPick): IBasePrice | null {\n if (pick.oddsState !== PickOddsState.Open) {\n return null;\n }\n const currentPrice = pick.currentPrice;\n if (currentPrice) {\n const { type, nativeOdds } = currentPrice;\n\n return { type, nativeOdds: { ...nativeOdds } };\n }\n\n return null;\n}\n\nexport function getPickPriceIds(pick: BetslipPick): number[] {\n if (BetslipV2ParticipantPick.isPick(pick) && pick.participants.length > 1) {\n return pick.prices.map((pr) => pr.id); // When we have pick with multiple participants return their price ids\n }\n const price = pick.currentPrice;\n if (price) {\n return [price.id];\n }\n\n return [];\n}\n\nexport function getSku(pick: BetslipPick): string {\n if (pick instanceof BetslipV2StandardPick) {\n const sportPick = pick;\n\n return [sportPick.sportId, sportPick.leagueId, sportPick.fixture.fixtureId, sportPick.option.id].join('-');\n }\n\n if (pick instanceof BetslipV1Pick) {\n const sportPick = pick;\n\n return [sportPick.sportId, sportPick.league.id, sportPick.event.id, sportPick.option.id].join('-');\n }\n\n if (pick instanceof BetslipBetBuilderPick) {\n const betBuilderPick = pick;\n\n return [betBuilderPick.sportId, betBuilderPick.leagueId, betBuilderPick.eventId, betBuilderPick.option.id].join('-');\n }\n\n if (pick instanceof BetslipV2ParticipantPick) {\n const racePick = pick;\n\n return [\n racePick.sportId,\n racePick.fixture.league ? racePick.leagueId : 0,\n racePick.fixture.fixtureId,\n (racePick.participants && racePick.participants.map((p) => p.fixtureParticipantId).join('_')) || racePick.id,\n ].join('-');\n }\n\n return '-';\n}\n\nexport function isEachWay(pickId: PickId, context: IBetslipState): boolean {\n if (context.overAskState.flowState !== OverAskFlowState.None) {\n const pick = context.overAskState.bets.flatMap((b) => b.picks).find((p) => p.pick.id.isEqual(pickId))!;\n\n if (pick.changes.marketType) {\n return pick.changes.marketType === OverAskMarketTypeChange.EachWay;\n }\n\n return pick.isEachWay;\n }\n\n switch (context.types.base.currentSelectedType) {\n case BetslipType.Single:\n return context.types.singleBet.picks[pickId.toString()].isEachWay;\n case BetslipType.Combo:\n return context.types.comboBet.isEachWay;\n case BetslipType.System:\n return context.types.systemBet.isEachWay;\n case BetslipType.EditBet:\n return false;\n default:\n throw new Error('No betslip type selected');\n }\n}\n\nexport function isSportPick(pick: BetslipPick): boolean {\n if (BetslipBetBuilderPick.isPick(pick)) {\n return false;\n }\n\n if (BetslipV2Pick.isPick(pick)) {\n return !isRacePick(pick);\n }\n\n return true;\n}\n\nexport function isRacePick(pick: BetslipV2Pick): pick is HorseRacePickType {\n return pick.fixture.sportId === SportConstant.Horses || pick.fixture.sportId === SportConstant.Greyhounds;\n}\n\nexport function isBOGRacePick(pick: BetslipV2Pick): pick is HorseRacePickType {\n return (\n (pick.fixture.sportId === SportConstant.Horses && pick instanceof BetslipV2ParticipantPick) ||\n (pick instanceof BetslipV2HorseRaceOptionMarketPick &&\n pick.pickType === ParticipantPickType.Win &&\n (pick.isStartingPriceAvailable() || pick.isFixedPriceAvailable())) ||\n pick.fixture.sportId === SportConstant.Greyhounds\n );\n}\n\nexport function isRaceParticipantPick(pick: BetslipV2Pick): pick is HorseRaceParticipantPickType {\n return pick.fixture.sportId === SportConstant.Horses && pick.id.getPickType() === PickType.V2ParticipantPick;\n}\n\nexport function isXCastPick(pick: BetslipPick) {\n return BetslipV2HorseRaceXCastPick.isPick(pick) || BetslipV2OptionMarketXCastRacePick.isPick(pick);\n}\n\nexport function isStartingPriceSelected(pick: BetslipPick): boolean {\n return (\n (pick instanceof BetslipV2HorseRaceWinParticipantPick || pick instanceof BetslipV2OptionMarketPick) &&\n pick.currentPrice?.type === PriceType.StartingPrice\n );\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector } from '../../base/store/selectors';\nimport { BetBuilderPickId } from '../../core/picks/pick-id';\nimport { flattenPicks, getLegsCount, getPicksCount, isBetBuilderPick } from '../../core/utils';\nimport { isPickEachWayCapable } from './services/betslip-pick-helpers';\n\nexport const betslipPickStateSelector = createSelector(betslipSelector, (state) => state.picks);\nexport const betslipPicksListSelector = createSelector(betslipPickStateSelector, (state) => state.pickList);\nexport const selectBetslipFlattenedPicksList = createSelector(betslipPicksListSelector, (pickList) => flattenPicks(pickList));\nexport const betslipPicksListCountSelector = createSelector(betslipPicksListSelector, (pickList) => getPicksCount(pickList));\nexport const betslipPicksListCountSelectorIncludeBetbuilderLegs = createSelector(betslipPicksListSelector, (pickList) => getLegsCount(pickList));\nexport const betslipPickIdsSelector = createSelector(betslipPickStateSelector, (state) => state.pickList.flatMap((p) => p.id));\nexport const betslipPicksUiSelector = createSelector(betslipPickStateSelector, (state) => state.ui);\n\nexport const pickWithErrorSelector = createSelector(betslipPickStateSelector, (state) => state.lastPickError);\n\nexport const emptyBetslipPicksListSelector = createSelector(betslipPicksListSelector, (state) => !state.length);\nexport const selectHasEachWayPick = createSelector(betslipPicksListSelector, (picks) => picks.some(isPickEachWayCapable));\n\nexport const selectBetBuilderPicksInfo = createSelector(betslipPicksListSelector, betslipPicksListCountSelector, (picks, betslipPicksCount) => {\n const betBuilderPicks = picks.filter((pick) => BetBuilderPickId.isId(pick.id));\n\n return {\n hasOnlyBetBuilderPicks: betslipPicksCount === betBuilderPicks.length,\n betBuilderPicksCount: betBuilderPicks.length,\n nonBetBuilderPicksCount: betslipPicksCount - betBuilderPicks.length,\n picks: betBuilderPicks,\n };\n});\nexport const selectPicksCountInfo = createSelector(betslipPicksListSelector, betslipPicksListCountSelector, (picks, betslipPicksCount) => {\n const betBuilderPicks = picks.filter((pick) => BetBuilderPickId.isId(pick.id));\n\n return {\n hasOnlyBetBuilderPicks: betslipPicksCount === betBuilderPicks.length,\n betBuilderPicksCount: betBuilderPicks.length,\n nonBetBuilderPicksCount: betslipPicksCount - betBuilderPicks.length,\n };\n});\n\nexport const selectNonBetBuilderPicks = createSelector(selectBetslipFlattenedPicksList, (picks) => picks.filter((pick) => !isBetBuilderPick(pick)));\n\nexport const selectBetslipNonBetBuilderPicksCount = createSelector(selectNonBetBuilderPicks, (pickList) => pickList.length);\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { selectBetBuilderPicksInfo, selectBetslipFlattenedPicksList, selectNonBetBuilderPicks } from '../picks/selectors';\n\nexport const betslipTypeStateSelector = createSelector(betslipSelector, (b) => b.types);\n\nexport const betslipTabsStateSelector = createSelector(\n betslipTypeStateSelector,\n selectBetBuilderPicksInfo,\n (betslipTypeState, betBuilderPicksState) => ({ betslipTypeState, betBuilderPicksState }),\n);\nexport const editBetStateSelector = createSelector(betslipTypeStateSelector, (state) => state.editBet);\nexport const editBetPicksListSelector = createSelector(betslipSelector, (state) => state.types.editBet.picks.pickList);\nexport const editBetPickStateSelector = createSelector(betslipTypeStateSelector, (state) => state.editBet.picks.pickState);\n\nexport const editBetAddedPicksListSelector = createSelector(betslipSelector, (state) => state.types.editBet.addPicksState.pickList);\n\nexport const editBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(editBetPicksListSelector, editBetAddedPicksListSelector, (pickList, pickAddedList) =>\n [...pickList, ...pickAddedList].find((p) => p.id['id'].toString() === pickId.toString()),\n );\n\nexport const betslipSelectedTypeStateSelector = createSelector(betslipTypeStateSelector, (b) => b.base.currentSelectedType);\n\nexport const selectIsSingleBetSelected = createSelector(betslipSelectedTypeStateSelector, (selected) => selected === BetslipType.Single);\nexport const selectSinglePicksState = createSelector(betslipTypeStateSelector, (b) => b.singleBet);\nexport const selectComboPicksState = createSelector(betslipTypeStateSelector, (b) => b.comboBet);\nexport const selectSystemPicksState = createSelector(betslipTypeStateSelector, (b) => b.systemBet);\nexport const selectBetBuilderPicksState = createSelector(betslipTypeStateSelector, (b) => b.betBuilder);\nexport const selectTeaserPicksState = createSelector(betslipTypeStateSelector, (b) => b.teaserBet);\n\nexport const selectSingleBetPicks = createSelector(\n selectSinglePicksState,\n selectBetslipFlattenedPicksList,\n selectNonBetBuilderPicks,\n selectIsLinearBetslip,\n (singleBet, picksList, nonBetBuilderPickList, isLinear) => {\n const picks = isLinear ? nonBetBuilderPickList : picksList;\n\n return picks.filter((pick) => singleBet.picks[pick.id.toString()]);\n },\n);\n\nexport const selectSystemBetPicks = createSelector(\n selectSystemPicksState,\n selectBetslipFlattenedPicksList,\n selectNonBetBuilderPicks,\n selectIsLinearBetslip,\n (systemBet, picksList, nonBetBuilderPickList, isLinear) => {\n const picks = isLinear ? nonBetBuilderPickList : picksList;\n\n return picks.filter((pick) => systemBet.picks[pick.id.toString()]);\n },\n);\n\nexport const selectSinglePicksCount = createSelector(selectSingleBetPicks, (picks) => picks.length);\nexport const selectComboPicksCount = createSelector(selectComboPicksState, (b) => Object.keys(b.picks).length);\nexport const selectSystemPicksCount = createSelector(selectSystemBetPicks, (picks) => picks.length);\nexport const selectBetBuilderPicksCount = createSelector(selectBetBuilderPicksState, (b) => Object.keys(b.picks).length);\nexport const selectTeaserPicksCount = createSelector(selectTeaserPicksState, (b) => Object.keys(b.picks).length);\nexport const selectTypesPickCount = createSelector(\n selectSinglePicksCount,\n selectComboPicksCount,\n selectSystemPicksCount,\n selectBetBuilderPicksCount,\n selectTeaserPicksCount,\n\n (singlePicksCount, comboPicksCount, systemPicksCount, betBuilderPicksCount, teaserPicksCount) => ({\n singlePicksCount,\n comboPicksCount,\n systemPicksCount,\n betBuilderPicksCount,\n teaserPicksCount,\n }),\n);\n\nexport const selectHasBetBuilderStatePicks = createSelector(selectBetBuilderPicksCount, (count) => count > 0);\n","import { BetslipType } from '../../core/betslip-type';\nimport { SlipId } from '../bet-generation/models';\nimport { BetslipError } from './errors/betslip-error';\nimport { ResultError } from './errors/result/result-error';\n\nexport interface IBetslipPickErrors {\n [pickId: string]: ResultError[];\n}\n\nexport type BetslipTypePickErrors = Record;\nexport type BetslipTypeErrors = Record;\nexport type SlipErrors = Record;\n\n// New error state necessary to support linear betslip\n// Try to avoid using the state directly and instead use one of the many selectors or create a new selector\nexport interface IBetslipErrorsState {\n pickErrors: BetslipTypePickErrors;\n slipErrors: SlipErrors;\n betslipTypeErrors: BetslipTypeErrors;\n betslipErrors: BetslipError[];\n}\n\nexport interface IBetslipLegacyErrorsState {\n pickErrors: IBetslipPickErrors;\n betslipErrors: BetslipError[];\n}\n\nexport const defaultErrorState: IBetslipErrorsState = {\n pickErrors: {\n SINGLE: {},\n COMBO: {},\n SYSTEM: {},\n TEASER: {},\n EDIT_MY_BET: {},\n BET_BUILDER: {},\n },\n slipErrors: {},\n betslipTypeErrors: {\n SINGLE: [],\n COMBO: [],\n SYSTEM: [],\n TEASER: [],\n EDIT_MY_BET: [],\n BET_BUILDER: [],\n },\n betslipErrors: [],\n};\n\nexport const emptyPickErrors = (): BetslipTypePickErrors => ({\n SINGLE: {},\n COMBO: {},\n SYSTEM: {},\n TEASER: {},\n EDIT_MY_BET: {},\n BET_BUILDER: {},\n});\n\nexport const emptyBetslipTypeErrors = (): BetslipTypeErrors => ({\n SINGLE: [],\n COMBO: [],\n SYSTEM: [],\n TEASER: [],\n EDIT_MY_BET: [],\n BET_BUILDER: [],\n});\n","import { Injectable } from '@angular/core';\n\nimport { LocalStoreService, NativeAppService, SessionStoreService } from '@frontend/vanilla/core';\n\nimport { ISavedState } from './state-storage.model';\n\n@Injectable({ providedIn: 'root' })\nexport default class StoragePersister {\n private readonly STORE_KEY = 'redux-store-v3';\n\n constructor(\n private session: SessionStoreService,\n private local: LocalStoreService,\n private nativeAppService: NativeAppService,\n ) {}\n\n /**\n * Apply the new saved state over the current saved and save it.\n * This will allow features to save state individually.\n *\n * @param state update on state\n */\n save(state: Partial): void {\n if (!this.nativeAppService.isTerminal) {\n this.local.set(this.STORE_KEY, state);\n\n return;\n }\n this.session.set(this.STORE_KEY, state);\n }\n\n load(): ISavedState | undefined {\n const savedState = !this.nativeAppService.isTerminal\n ? this.local.get(this.STORE_KEY)\n : this.session.get(this.STORE_KEY);\n\n return savedState ? savedState : undefined;\n }\n}\n","import { TemplateRef } from '@angular/core';\n\nimport { OddsAcceptanceMode } from '@bpos';\nimport { OfferSource } from '@cds';\nimport { Fixture } from '@cds/betting-offer';\nimport { PicksView } from '@cds/betting-offer/domain-specific';\nimport { EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { pprops } from '@frontend/sports/common/core/utils/redux';\nimport { createAction, props } from '@ngrx/store';\nimport { BetBuilderMarket } from 'packages/sports/web/app/src/betbuilder/model/bet-builder.model';\nimport { OptionRequest } from 'packages/sports/web/app/src/navigation-core/pick-uri.model';\n\nimport { EditMyBetPicksPayload } from '../model/edit-mybet/edit-bet-init';\nimport { BetStationFreeBetToken } from '../modules/reward-tokens/reward-tokens.model';\nimport { BetslipType } from './betslip-type';\nimport { BetslipGroupInformation } from './groups/betslip-group-information';\nimport { BetBuilderPickId, PickId, V1PickId, V2OptionMarketPickId, V2OptionMarketXCastPickId, V2ParticipantPickId } from './picks/pick-id';\nimport { IPickTracking } from './picks/pick-models';\n\nexport interface BetBuilderV1EventData {\n event: {\n id: string;\n name: string;\n date: Date;\n isLive?: boolean;\n groupId?: number;\n };\n league: {\n id: number;\n name: string;\n parentLeagueId?: number;\n realCompetitionId?: number;\n };\n region: {\n id: number;\n name: string;\n };\n offerSource: OfferSource;\n betBuilderTradingV2FixtureId?: string;\n}\n\nexport interface PickAddPayload {\n pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId | V2OptionMarketXCastPickId;\n tracking: IPickTracking; // Pick source tracking information\n priceId?: number; // In case when pick can have multiple prices the call can choose which one to be selected ( optional )\n isPriceBoosted?: boolean;\n groupInfo?: BetslipGroupInformation;\n parentLinkedEventId?: string;\n isBetBuilder?: boolean;\n}\nexport interface PickRemovePayload {\n pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId;\n tracking: IPickTracking; // Pick source tracking information\n priceId?: number; // In case when pick can have multiple prices the call can choose which one to be selected ( optional )\n isPriceBoosted?: boolean;\n}\n\nexport interface BABAdditionalInfo {\n sportcastId?: number;\n betgeniusId?: number;\n useV2Key?: boolean;\n longIds?: string[];\n}\n\nexport enum BetslipHost {\n Digital = 'Digital',\n BetStation = 'BetStation',\n}\n\nexport class ExternalBetslipActions {\n static readonly ACTION_SCHEMA = `PICKS`;\n\n private static readonly ADD_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_PICK`;\n private static readonly ADD_GROUP_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_GROUP_PICK`;\n private static readonly ADD_MULTIPLE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_MULTIPLE_PICKS`;\n private static readonly ADD_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_BET_BUILDER_PICK`;\n private static readonly ECHO_ADD_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADDED_BET_BUILDER_PICK`;\n private static readonly ECHO_ADD_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADDED_PICK`;\n private static readonly ADD_ENTAIN_UI_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_ENTAIN_UI_BET_BUILDER_PICK`;\n private static readonly REMOVE_ENTAIN_UI_BET_BUILDER_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_ENTAIN_UI_BET_BUILDER_PICK`;\n private static readonly CHECK_PRECREATED_BAB_CONVERSION = `${ExternalBetslipActions.ACTION_SCHEMA}/CHECK_PRECREATED_BAB_CONVERSION`;\n\n private static readonly REMOVE_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_PICK`;\n private static readonly ECHO_REMOVE_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVED_PICK`;\n private static readonly REMOVE_MULTIPLE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_MULTIPLE_PICKS`;\n private static readonly ECHO_REMOVE_MULTIPLE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVED_MULTIPLE_PICKS`;\n private static readonly REMOVE_PICKS_FROM_GROUP = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_PICKS_FROM_GROUP`;\n\n private static readonly REQUEST_BETSLIP_TYPE = `${ExternalBetslipActions.ACTION_SCHEMA}/REQUEST_BETSLIP_TYPE`;\n\n private static readonly REQUEST_ADD_AFFILIATE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/REQUEST_ADD_AFFILIATE_PICKS`;\n private static readonly SHARE_AFFILIATE_PICKS = `${ExternalBetslipActions.ACTION_SCHEMA}/SHARE_AFFILIATE_PICKS`;\n private static readonly START_EDIT_MY_BET = `${ExternalBetslipActions.ACTION_SCHEMA}/START_EDIT_MY_BET`;\n\n private static readonly REQUEST_NOTIFICATION_SETTING_UPDATE = `${ExternalBetslipActions.ACTION_SCHEMA}/REQUEST_NOTIFICATION_SETTING_UPDATE`;\n\n private static readonly ADD_WEB_APP_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/ADD_WEB_APP_PICK`;\n private static readonly REMOVE_WEB_APP_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/REMOVE_WEB_APP_PICK`;\n\n private static readonly SET_REWARD_TOKEN = `${ExternalBetslipActions.ACTION_SCHEMA}/SET_REWARD_TOKEN`;\n private static readonly TOGGLE_WIN_PICK = `${ExternalBetslipActions.ACTION_SCHEMA}/TOGGLE_WIN_PICK`;\n\n /**\n * Action called with request to add v1 or option market pick to betslip,\n * Optionally set stake value when first pick to override the default stake.\n */\n static addPick = createAction(ExternalBetslipActions.ADD_PICK, props());\n\n /**\n * Action called with request to add several v1 or option market picks in a SGP group to betslip\n */\n static addGroupPick = createAction(\n ExternalBetslipActions.ADD_GROUP_PICK,\n props<{ picks: PickAddPayload[]; headerTemplate?: TemplateRef }>(),\n );\n\n /**\n * Action called with request to add v1 or option market pick to betslip\n */\n\n static addMultiplePicks = createAction(ExternalBetslipActions.ADD_MULTIPLE_PICKS, props<{ picks: PickAddPayload[]; stake?: number }>());\n /**\n * Action called with request to add bet builder pick to betslip, when v1\n */\n static addBetBuilderPick = createAction(\n ExternalBetslipActions.ADD_BET_BUILDER_PICK,\n props<{ pickId: BetBuilderPickId; tracking: IPickTracking; eventData: BetBuilderV1EventData | null; additionalInfo?: BABAdditionalInfo }>(),\n );\n\n /**\n * Action called with request to add entain ui bet builder pick to betslip\n */\n static addEntainUiBetBuilderPick = createAction(\n ExternalBetslipActions.ADD_ENTAIN_UI_BET_BUILDER_PICK,\n props<{\n pickId: BetBuilderPickId;\n tracking: IPickTracking;\n eventModel: EventModel;\n betBuilderOptionMarket: BetBuilderMarket;\n eventData: BetBuilderV1EventData;\n longId: string;\n }>(),\n );\n\n /**\n * Action called with request to remove entain ui bet builder pick to betslip\n */\n static removeEntainUiBetBuilderPick = createAction(\n ExternalBetslipActions.REMOVE_ENTAIN_UI_BET_BUILDER_PICK,\n props<{\n pickId: BetBuilderPickId;\n longId: string;\n eventId: string;\n }>(),\n );\n\n /**\n * Action called with request to check if there is already a customized BAB for the event on the betslip\n */\n static checkPrecreatedBABConversion = createAction(\n ExternalBetslipActions.CHECK_PRECREATED_BAB_CONVERSION,\n props<{\n pickId: BetBuilderPickId;\n eventModel: EventModel;\n market: BetBuilderMarket;\n eventData: BetBuilderV1EventData;\n longId: string[];\n headerTemplate: TemplateRef;\n isBetslip?: boolean;\n }>(),\n );\n\n /**\n * Action called when pick is added to betslip\n */\n static echoAddPick = createAction(ExternalBetslipActions.ECHO_ADD_PICK, props());\n\n /**\n * Action called with request to add bet builder pick to betslip on betstation cross screen\n */\n static echoAddBetBuilderPick = createAction(\n ExternalBetslipActions.ECHO_ADD_BET_BUILDER_PICK,\n props<{ pickId: BetBuilderPickId; tracking: IPickTracking; eventData: BetBuilderV1EventData | null; additionalInfo?: BABAdditionalInfo }>(),\n );\n\n /**\n * Action called with request to remove pick from betslip\n */\n static removePick = createAction(ExternalBetslipActions.REMOVE_PICK, props<{ pickId: PickId }>());\n\n /**\n * Action called when pick is removed from betslip\n */\n static echoRemovePick = createAction(ExternalBetslipActions.ECHO_REMOVE_PICK, props<{ pickId: PickId }>());\n\n /**\n * Remove picks from the slip\n */\n static removeMultiplePicks = createAction(ExternalBetslipActions.REMOVE_MULTIPLE_PICKS, props<{ pickIds: PickId[] }>());\n\n /**\n * Action called when pick is removed from betslip\n */\n static echoRemoveMultiplePicks = createAction(ExternalBetslipActions.ECHO_REMOVE_MULTIPLE_PICKS, props<{ pickIds: PickId[] }>());\n\n /**\n * Remove picks from group in the slip\n */\n static removePicksFromGroup = createAction(ExternalBetslipActions.REMOVE_PICKS_FROM_GROUP, props<{ pickIds: PickId[] }>());\n\n /**\n * Action called in order betslip to change its type\n */\n static requestBetslipType = createAction(ExternalBetslipActions.REQUEST_BETSLIP_TYPE, props<{ betslipType: BetslipType }>());\n\n /**\n * Action called when affiliate deep link is found\n */\n static requestAddAffiliatePicks = createAction(\n ExternalBetslipActions.REQUEST_ADD_AFFILIATE_PICKS,\n props<{ request: OptionRequest; picksView: PicksView }>(),\n );\n\n /**\n * Action called to share bet (in course of affiliate deep link)\n */\n static shareBet = createAction(ExternalBetslipActions.SHARE_AFFILIATE_PICKS);\n\n /**\n * Action called when bet editing is started\n */\n static startEditMyBet = createAction(ExternalBetslipActions.START_EDIT_MY_BET, props<{ payload: EditMyBetPicksPayload }>());\n\n /**\n * Action called to update (all - e.g. email or app) notification settings\n */\n static requestNotificationSettingUpdate = createAction(\n ExternalBetslipActions.REQUEST_NOTIFICATION_SETTING_UPDATE,\n props<{ oddsAcceptance?: OddsAcceptanceMode; emailNotify?: boolean; appNotify?: boolean }>(),\n );\n\n /**\n * Action called to add a pick via web bridge service (e.g. used for AB testing)\n */\n static addWebAppPick = createAction(\n ExternalBetslipActions.ADD_WEB_APP_PICK,\n props<{\n fixture: Fixture;\n option: number;\n }>(),\n );\n /**\n \n * Action called to remove a pick via web bridge service (e.g. used for AB testing)\n \n */\n\n static removeWebAppPick = createAction(\n ExternalBetslipActions.REMOVE_WEB_APP_PICK,\n\n props<{\n fixture: Fixture;\n option: number;\n }>(),\n );\n\n /**\n * Action called to set a reward token (e.g. via free-bet.service)\n */\n static setBetstationFreeBetToken = createAction(ExternalBetslipActions.SET_REWARD_TOKEN, pprops());\n\n /**\n * Action called when Toggle Win pick is called\n */\n static toggleWinPick = createAction(\n ExternalBetslipActions.TOGGLE_WIN_PICK,\n props<{ pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId }>(),\n );\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { selectIsLinearBetslip } from '../../../base/store/selectors';\nimport { BetslipType } from '../../../core/betslip-type';\nimport { betslipTypeStateSelector } from '../selectors';\n\nexport const betslipTypesBaseSelector = createSelector(betslipTypeStateSelector, (b) => b.base);\nexport const betslipCurrentTypeSelector = createSelector(betslipTypesBaseSelector, (b) => b.currentSelectedType);\nexport const betslipActiveTypesSelector = createSelector(betslipTypesBaseSelector, (b) => b.activeTypes);\nexport const selectLinearTypes = createSelector(betslipTypesBaseSelector, (b) => b.linearTypes);\n\nexport const selectIsLinearSingleBetBuilder = createSelector(\n selectIsLinearBetslip,\n selectLinearTypes,\n (isLinear, linearTypes) => isLinear && !linearTypes[BetslipType.Combo] && linearTypes[BetslipType.BetBuilder],\n);\n","export enum BetPlacementErrorIcon {\n Warning,\n Error,\n}\n","import { CashoutErrorType } from '@bpos/v1/cashout';\nimport { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetPlacementErrorIcon } from './bet-placement-error-icon';\nimport { ClientErrorType } from './client-error-type';\n\nexport enum ErrorOrigin {\n // Error comes from the server\n BetPlacement = 'BetPlacement',\n // Error is created on the client.\n BetSlip = 'BetSlip',\n}\n\nexport enum CleanStrategy {\n // Is the error is cleaned on every action.\n Always = 'Always',\n // Is the error cleaned on manual action ( Errors from BPS, Some Warnings. )\n Manually = 'Manually',\n}\n\nexport interface BetslipErrorRestrictions {\n betslipType: BetslipType;\n slipTypeKey?: string;\n}\n\nexport interface BetslipErrorSaved {\n // Error type string name of the error.\n type: PlacementErrorType | CashoutErrorType | ClientErrorType;\n // Error icon to display.\n icon: BetPlacementErrorIcon;\n // Priority of the error. Lower is bigger. We display only one error, so errors with lower priority will be displayed first.\n priority: number;\n // The source of the error.\n origin: ErrorOrigin;\n // The strategy to remove the error.\n cleanStrategy: CleanStrategy;\n // Does the error have client validation ?\n hasClientValidation: boolean;\n // Should we display a message to the user for this error.\n isHidden: boolean;\n // The error code of the error.\n code: string;\n // Is this error restricted to a betslip type or slip type?\n restrictions?: BetslipErrorRestrictions;\n}\n\nexport class BetslipError implements BetslipErrorSaved {\n // Error type string name of the error.\n type: PlacementErrorType | CashoutErrorType | ClientErrorType;\n // Error icon to display.\n icon: BetPlacementErrorIcon = BetPlacementErrorIcon.Error;\n // Priority of the error. Lower is bigger. We display only one error, so errors with lower priority will be displayed first.\n priority = 0;\n // The source of the error.\n origin: ErrorOrigin = ErrorOrigin.BetSlip;\n // The strategy to remove the error.\n cleanStrategy: CleanStrategy = CleanStrategy.Always;\n // Does the error have client validation ?\n hasClientValidation = false;\n // Should we display a message to the user for this error.\n isHidden = false;\n // The error code of the error.\n code: string;\n\n slipId: string;\n\n sportId: number | null;\n\n /**\n * Is one error equals to another.\n *\n * @param error\n */\n equals(error: BetslipError): boolean {\n // Return false always.\n return false;\n }\n\n copy(): this {\n return Object.assign(Object.create(Object.getPrototypeOf(this)), this);\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { PosError } from '../../../base/utils/pos-errors';\nimport { BetslipError, BetslipErrorSaved } from './betslip-error';\n\nexport interface BetPlacementErrorSaved extends BetslipErrorSaved {\n type: PlacementErrorType;\n errorMessage: string;\n newStakeHint?: number;\n}\n\nexport class BetPlacementError extends BetslipError implements BetPlacementErrorSaved {\n constructor() {\n super();\n }\n\n override type: PlacementErrorType;\n errorMessage: string;\n platformError: PosError;\n newStakeHint?: number;\n detailedErrorCode?: string;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementError } from '../bet-placement-error';\n\nexport class UserError extends BetPlacementError {\n constructor() {\n super();\n this.type = PlacementErrorType.UserError;\n }\n}\n","import { UserError } from './user/user-error';\n\nexport class StakeError extends UserError {\n constructor() {\n super();\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { StakeError } from '../stake-error';\n\nexport class UnderMinimumStake extends StakeError {\n constructor() {\n super();\n this.icon = BetPlacementErrorIcon.Warning;\n this.hasClientValidation = true;\n this.type = PlacementErrorType.StakeBelowMinimumLimit;\n }\n}\n","import { BetslipError, CleanStrategy, ErrorOrigin } from '../betslip-error';\n\nexport interface PreCheckErrorMessages {\n AllowedStakes: string;\n ComboPrevention: string;\n ComboPreventionSameEvent: string;\n ForecastSystemBet: string;\n FreeBetAdded: string;\n minComboBetsCount: string;\n MinimumCombo: string;\n MinStakePerBet: string;\n MinStakeTotal: string;\n MinStakeValue: string;\n minSystemBetsCount: string;\n BetbuilderSystemNotAllowed: string;\n MultipleSinglesNotAllowed: string;\n NotEnoughMoney: string;\n NotMultiple: string;\n PickAdded: string;\n PickAlreadyAdded: string;\n PickNotFound: string;\n PickRemoved: string;\n PicksMaximum: string;\n LessStakeAfterTax: string;\n SelectExactRaceSystemBets: string;\n SelectMaxComboBets: string;\n SelectMaxRaceComboBets: string;\n SelectMaxSystemBets: string;\n SelectMinComboBets: string;\n SelectMinComboBetsLinear: string;\n SelectMinSingleBets: string;\n SelectMinSystemBets: string;\n SelectMaxSystemBetsBanker: string;\n SelectMinSystemBetsBanker: string;\n SelectMaxTeaserBets: string;\n MinTeaserBetsCount: string;\n SelectMinTeaserBets: string;\n}\n\n// We initially used the message key directly matching an error to a specific message type, this means we couldn't have specific messages for linear\n// Solution:\n// - New abstraction here (ErrorDetailSpecifier)\n// - Resolve the ErrorDetailSpecifier to a Message Key in a later step\nexport enum ErrorDetailsSpecifier {\n MinComboBetsCount,\n MinSystemBetsCount,\n MinTeaserBetsCount,\n PicksMaximum,\n SelectMaxComboBets,\n SelectMaxRaceComboBets,\n SelectMaxSystemBets,\n SelectMinComboBets,\n SelectMinSingleBets,\n SelectMinSystemBets,\n SelectMaxSystemBetsBanker,\n SelectMinSystemBetsBanker,\n SelectMinTeaserBets,\n SelectMaxTeaserBets,\n ForecastSystemBet,\n}\n\nexport interface IPickPreCheckError {\n pickId: string;\n}\n\ninterface TClassType {\n new (...args: any[]): T;\n prototype: T;\n}\n\nexport function PreCheckErrorMixin = TClassType>(\n // eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match\n BaseErrorType: TBaseClass,\n): TBaseClass {\n return class extends BaseErrorType {\n constructor(...args: any[]) {\n super(...args);\n this.origin = ErrorOrigin.BetSlip;\n this.cleanStrategy = CleanStrategy.Always;\n this.hasClientValidation = true;\n }\n };\n}\n","import { Decimal } from 'decimal.js';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { UnderMinimumStake } from '../general/under-minimum-stake';\nimport { PreCheckErrorMixin } from './pre-check-error';\nimport { ITypeRestrictedPreCheckError } from './type-restricted-pre-check-error.interface';\n\nexport class UnderMinimumStakePreCheckError extends PreCheckErrorMixin(UnderMinimumStake) {\n constructor(\n readonly minimumStake: Decimal,\n readonly betStake: Decimal,\n readonly numberOfBetsInBet: number,\n readonly pickId: string,\n teaserId: number,\n ) {\n super();\n this.sportId = typeof teaserId === 'undefined' ? null : teaserId;\n }\n\n getMinimumStake = () => this.minimumStake;\n}\n\nexport class BetslipTypeUnderMinimumStakePreCheckError extends UnderMinimumStakePreCheckError implements ITypeRestrictedPreCheckError {\n constructor(\n minimumStake: Decimal,\n betStake: Decimal,\n numberOfBetsInBet: number,\n pickId: string,\n teaserId: number,\n private errorBetslipType: BetslipType,\n private errorSlipTypeKey?: string,\n ) {\n super(minimumStake, betStake, numberOfBetsInBet, pickId, teaserId);\n }\n\n get betslipType(): BetslipType {\n return this.errorBetslipType;\n }\n\n get slipTypeKey(): string | undefined {\n return this.errorSlipTypeKey;\n }\n}\n","import { BetslipType } from '../../core/betslip-type';\nimport { SystemSlipType } from '../bet-generation/slip-type';\nimport { ISingleBetPickState, ISingleBetState } from '../single-bet/state';\nimport { ISystemBetState } from '../system-bet/state';\nimport { IBetslipTypeState } from '../types/state';\nimport { BetslipError } from '../validation/errors/betslip-error';\nimport { UnderMinimumStakePreCheckError } from '../validation/errors/pre-check/under-minimum-stake-pre-check-error';\n\nexport function GetSingleBetStake(singleBet: ISingleBetState, pickId: string): string | null {\n return singleBet.picks[pickId]?.actualStake;\n}\n\nexport function GetSummaryStake(types: IBetslipTypeState): string | null {\n if (types.base.currentSelectedType === BetslipType.Combo) {\n return types.comboBet?.actualStake;\n } else if (types.base.currentSelectedType === BetslipType.System) {\n return types.systemBet?.actualStake;\n } else if (types.base.currentSelectedType === BetslipType.Teaser) {\n return types.teaserBet.actualStake;\n }\n\n return null;\n}\n\nexport function GetStakeForSystemType(systemBetState: ISystemBetState, type: SystemSlipType): string | null {\n const typeStakes = systemBetState.linearTypeStakes ?? {};\n\n return typeStakes[type.key].actualStake ?? null;\n}\n\nconst MAX_POSSIBLE_DIGIT = '9';\n\nconst canPotentialyTypeDigitsToReachMinStake = (stake: string, minimumStake: string) => {\n if (stake.indexOf('.') < 0 && minimumStake.indexOf('.') >= 0) {\n return true;\n }\n let highestPotentialStake = stake;\n const minStakeDecimalPlaces = minimumStake.split('.')[1]?.length || 0;\n const stakeDecimalPlaces = stake.split('.')[1]?.length || 0;\n for (let i = 0; i < minStakeDecimalPlaces - stakeDecimalPlaces; i++) {\n highestPotentialStake += MAX_POSSIBLE_DIGIT;\n }\n if (highestPotentialStake.endsWith('.')) {\n highestPotentialStake = highestPotentialStake.substring(0, highestPotentialStake.length - 1);\n }\n\n return Number(highestPotentialStake) >= Number(minimumStake);\n};\n\nexport const HideMinError = (minimumStake: number, isNumpadOpen: boolean, totalStake: string | null) => {\n if (!totalStake) {\n return true;\n }\n\n const actualStake = Number(totalStake);\n if (isNumpadOpen && actualStake < minimumStake) {\n return canPotentialyTypeDigitsToReachMinStake(totalStake, Number(minimumStake).toString());\n }\n\n return actualStake >= minimumStake;\n};\n\nexport function isSameStakeAppliedToAllSingles(picks: Record): boolean {\n const stakes = Object.values(picks)\n .filter((pick) => pick.isSelected)\n .map((pick) => pick.stake);\n\n return new Set(stakes).size === 1;\n}\n\nexport function validateStake(stakeError: BetslipError | undefined, actualStake: string | null, isNumpadOpen: boolean): BetslipError | null {\n if (!stakeError) {\n return null;\n }\n\n if (!stakeError.hasClientValidation) {\n return null;\n }\n\n if (stakeError instanceof UnderMinimumStakePreCheckError) {\n const shouldHide = HideMinError(stakeError.minimumStake.toNumber(), isNumpadOpen, actualStake);\n if (shouldHide) {\n return null;\n }\n }\n\n return stakeError;\n}\n\nexport function parseStakeFieldStake(stakeString: string | null | undefined): number | null {\n const numberStake = stakeString ? +stakeString : null;\n\n if (numberStake === 0) {\n // 0 stake should count as no stake being entered\n return null;\n }\n\n return numberStake;\n}\n","import { createSelector } from '@ngrx/store';\nimport { pickBy } from 'lodash-es';\n\nimport { PickId } from '../../core/picks/pick-id';\nimport { IStake } from '../stake/models';\nimport { isSameStakeAppliedToAllSingles } from '../stake/utils';\nimport { betslipTypeStateSelector, selectIsSingleBetSelected } from '../types/selectors';\nimport { ISingleBetPickState } from './state';\n\nexport const singleBetStateSelector = createSelector(betslipTypeStateSelector, (typeState) => typeState.singleBet);\nexport const singleBetPicksSelector = createSelector(singleBetStateSelector, (s) => s.picks);\n\nexport const isMultiSingleBetSelector = createSelector(\n singleBetPicksSelector,\n (singleBetState) => Object.values(singleBetState).filter((pick) => pick.isSelected).length > 1,\n);\n\nexport const singleBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(singleBetPicksSelector, (picks): ISingleBetPickState | undefined => {\n return picks[pickId.toString()];\n });\n\nexport const selectSingleBetPickStakeFactory = (pickId: PickId) =>\n createSelector(singleBetPickSelectorFactory(pickId), (pick): IStake | null => {\n return pick ? { actualStake: pick.actualStake, stake: pick.stake } : null;\n });\n\nexport const singleBetSelectedPicksSelector = createSelector(singleBetPicksSelector, (picks) => pickBy(picks, (pick) => pick.isSelected));\n\nexport const singleBetPicksGeneralStakeSelector = createSelector(singleBetPicksSelector, (picks) => {\n const singleStates = Object.values(picks);\n\n if (singleStates.length < 1) {\n return null;\n }\n\n const firstState = singleStates[0];\n\n const stake = firstState.stake;\n\n if (singleStates.every((singleState) => singleState.stake === stake)) {\n return { stake, actualStake: firstState.actualStake };\n }\n\n return { stake: null, actualStake: null };\n});\n\nexport const selectSingleBetAllSameStake = createSelector(selectIsSingleBetSelected, singleBetPicksSelector, (isSingleBet, picks) => {\n return isSingleBet && isSameStakeAppliedToAllSingles(picks);\n});\n\nexport const selectSingleBetSinglePickState = createSelector(singleBetSelectedPicksSelector, (selectedPicks) => {\n const states = Object.values(selectedPicks);\n\n return states.length === 1 ? states[0] : null;\n});\n\nexport const selectSingleBetSinglePickId = createSelector(singleBetSelectedPicksSelector, (selectedPicks) => {\n const states = Object.keys(selectedPicks);\n\n return states.length === 1 ? states[0] : null;\n});\n","/// \n/// System bet type, \n/// \nexport enum SystemType {\n // Singles = N combinations\n SystemSingle = 0,\n // Combo = 1 combination\n SystemCombo = 1,\n\n /// \n /// 3 combo bets 2/3\n /// \n System2Of3 = 2, // Make it equal to 2 so to unify it with BetSlipType\n\n /// \n /// 3 combo bets 2/3 + 1 combo bet 3/3 + 3 single bets\n /// \n SystemPatent,\n\n /// \n /// 3 combo bets 2/3 + 1 combo bet 3/3\n /// \n SystemTrixie,\n\n /// \n /// 6 combo bets 2/4\n /// \n System2Of4,\n\n /// \n /// 4 combo bets 3/4\n /// \n System3Of4,\n\n /// \n /// 6 combo bets 2/4 + 4 combo bets 3/4 + 1 combo bet 4/4\n /// \n SystemYankee,\n\n /// \n /// 6 combo bets 2/4 + 4 combo bets 3/4 + 1 combo bet 4/4 + 4 single bets\n /// \n SystemLucky15,\n\n /// \n /// 10 combo bets 2/5\n /// \n System2Of5,\n\n /// \n /// 10 combo bets 3/5\n /// \n System3Of5,\n\n /// \n /// 5 combo bets 4/5\n /// \n System4Of5,\n\n /// \n /// 10 combo bets 2/5 + 10 combo bets 3/5 + 5 combo bets 4/5 + 1 combo bet 5/5\n /// \n SystemCanadian,\n\n /// \n /// 10 combo bets 2/5 + 10 combo bets 3/5 + 5 combo bets 4/5 + 1 combo bet 5/5 + 5 single bets\n /// \n SystemLucky31,\n\n /// \n /// 15 combo bets 2/6\n /// \n System2Of6,\n\n /// \n /// 20 combo bets 3/6\n /// \n System3Of6,\n\n /// \n /// 15 combo bets 4/6\n /// \n System4Of6,\n\n /// \n /// 6 combo bets 5/6\n /// \n System5Of6,\n\n /// \n /// 15 combo bets 2/6 + 20 combo bets 3/6 + 15 combo bets 4/6 + 6 combo bets 5/6 + 1 combo bet 6/6\n /// \n SystemHeinz,\n\n /// \n /// 15 combo bets 2/6 + 20 combo bets 3/6 + 15 combo bets 4/6 + 6 combo bets 5/6 + 1 combo bet 6/6 + 6 single bets\n /// \n SystemLucky63,\n\n /// \n /// 21 combo bets 2/7\n /// \n System2Of7,\n\n /// \n /// 35 combo bets 3/7\n /// \n System3Of7,\n\n /// \n /// 35 combo bets 4/7\n /// \n System4Of7,\n\n /// \n /// 21 combo bets 5/7\n /// \n System5Of7,\n\n /// \n /// 7 combo bets 6/7\n /// \n System6Of7,\n\n /// \n /// 21 combo bets 2/7 + 35 combo bets 3/7 + 35 combo bets 4/7 + 21 combo bets 5/7 + 7 combo bets 6/7 + 1 combo bet 7/7\n /// \n SystemSuperHeinz,\n\n /// \n /// 28 combo bets 2/8\n /// \n System2Of8,\n\n /// \n /// 56 combo bets 3/8\n /// \n System3Of8,\n\n /// \n /// 70 combo bets 4/8\n /// \n System4Of8,\n\n /// \n /// 56 combo bets 5/8\n /// \n System5Of8,\n\n /// \n /// 28 combo bets 6/8\n /// \n System6Of8,\n\n /// \n /// 8 combo bets 7/8\n /// \n System7Of8,\n\n /// \n /// 28 combo bets 2/8 + 56 combo bets 3/8 + 70 combo bets 4/8 + 56 combo bets 5/8 + 28 combo bets 6/8 + 8 combo bets 7/8 + 1 combo bet 8/8\n /// \n SystemGoliath,\n\n /// \n /// Banker bets + single bets.\n /// \n SystemBankerSingles,\n /// \n /// Multi single bets, used in betstation to place multiple bets as atomic transaction.\n /// \n SystemSingles,\n}\n\nexport enum SlipTypeKeys {\n Single = 'single',\n Combo = 'combo',\n BetBuilder = 'betbuilder',\n}\n","import { SystemSlipType } from '../bet-generation/slip-type';\n\nexport enum SystemKey {\n Patent = 'patent',\n Lucky15 = 'lucky15',\n Lucky31 = 'lucky31',\n Lucky63 = 'lucky63',\n Trixie = 'trixie',\n Yankee = 'yankee',\n Canadian = 'canadian',\n Heinz = 'heinz',\n SuperHeinz = 'superheinz',\n Goliath = 'goliath',\n // eslint-disable-next-line @typescript-eslint/naming-convention\n MOfN = 'mofn',\n BankerSingles = 'systembankersingles',\n}\n\nexport enum SystemModesLinearMOfN {\n System2of3 = 'system2of3',\n System2of4 = 'system2of4',\n System2of5 = 'system2of5',\n System2of6 = 'system2of6',\n System2of7 = 'system2of7',\n System2of8 = 'system2of8',\n System3of4 = 'system3of4',\n System3of5 = 'system3of5',\n System3of6 = 'system3of6',\n System3of7 = 'system3of7',\n System3of8 = 'system3of8',\n System4of5 = 'system4of5',\n System4of6 = 'system4of6',\n System4of7 = 'system4of7',\n System4of8 = 'system4of8',\n System5of6 = 'system5of6',\n System5of7 = 'system5of7',\n System5of8 = 'system5of8',\n System6of7 = 'system6of7',\n System6of8 = 'system6of8',\n System7of8 = 'system7of8',\n}\n\nexport interface SystemModes {\n canadian: string;\n goliath: string;\n heinz: string;\n lucky15: string;\n lucky31: string;\n lucky63: string;\n patent: string;\n superheinz: string;\n system2of3: string;\n system2of4: string;\n system2of5: string;\n system2of6: string;\n system2of7: string;\n system2of8: string;\n system3of4: string;\n system3of5: string;\n system3of6: string;\n system3of7: string;\n system3of8: string;\n system4of5: string;\n system4of6: string;\n system4of7: string;\n system4of8: string;\n system5of6: string;\n system5of7: string;\n system5of8: string;\n system6of7: string;\n system6of8: string;\n system7of8: string;\n trixie: string;\n yankee: string;\n}\n\nexport interface ISlipInfo {\n slipType: SystemSlipType;\n betCount: number;\n}\n\nexport enum LinearSystemBetTracking {\n SystemBetPositionEvent = 'Sytem bet expand/collapse',\n HidePickListEventDetails = 'Collapse system bet',\n ViewPickListEventDetails = 'Expand system bet',\n SystemBetPosition = 'system bet',\n}\n\nexport interface ISystemSlipTypeInfo {\n slipType: SystemSlipType;\n betCount: number;\n typeName: string;\n}\n\nexport const LinearSystemBetPositionEvent = 'Linear System Bet Info Popup';\nexport const LinearSystemBetEventDetailsOpened = 'System Bet Info Icon Open';\nexport const LinearSystemBetEventDetailsClosed = 'System Bet Info Icon Closed';\nexport const LinearSystemBetActionEvent = 'Click';\n","import { SystemKey } from '../system-bet/models';\nimport { SlipTypeKeys } from './betslip-system-type';\n\nexport abstract class SlipType {\n private _key: string;\n private _name: string;\n private _basis: number[];\n\n constructor(key: string, name: string, basis: number[]) {\n this._key = key;\n this._name = name;\n this._basis = basis;\n }\n\n get key(): string {\n return this._key;\n }\n\n get name(): string {\n return this._name;\n }\n\n get basis(): number[] {\n return this._basis;\n }\n\n equals(object: SlipType | null | undefined): boolean {\n if (!object) {\n return false;\n }\n\n return this.key === object.key && SlipType.isBasisEqual(this.basis, object.basis);\n }\n\n toJSON(): { key: string; name: string; basis: number[] } {\n return {\n key: this.key,\n name: this.name,\n basis: this.basis,\n };\n }\n\n private static isBasisEqual(thisBasis: number[], otherBasis: number[]): boolean {\n if (thisBasis.length !== otherBasis.length) {\n return false;\n }\n for (let i = 0; i < thisBasis.length; i++) {\n if (thisBasis[i] !== otherBasis[i]) {\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport class SingleSlipType extends SlipType {\n constructor() {\n super(SlipTypeKeys.Single, '', [1]);\n }\n}\n\nexport class ComboSlipType extends SlipType {\n constructor() {\n super(SlipTypeKeys.Combo, '', [-1]);\n }\n}\n\nexport class SystemSlipType extends SlipType {\n constructor(key: string, name: string, basis: number[]) {\n super(key, name, basis);\n }\n}\n\nexport class SystemBankerSinglesSlipType extends SystemSlipType {\n constructor() {\n super(SystemKey.BankerSingles, '', [1]);\n }\n}\n","import { BetslipType } from '../../../core/betslip-type';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { PickId } from '../../../core/picks/pick-id';\nimport { SlipTypeKeys } from '../betslip-system-type';\nimport { ISlip, SlipId } from '../models';\nimport { ComboSlipType, SingleSlipType, SystemSlipType } from '../slip-type';\n\nconst SYSTEM_SLIP_ID_PREFIX = 'system__';\nconst BET_BUILDER_SLIP_ID_PREFIX = `${SlipTypeKeys.BetBuilder}__`;\nconst SINGLE_SLIP_ID_PREFIX = `${SlipTypeKeys.Single}__`;\n\nexport function getSlipBetslipType(slip: ISlip): BetslipType | undefined {\n const type = slip.type;\n\n if (type instanceof SingleSlipType) {\n return BetslipType.Single;\n }\n\n if (type instanceof ComboSlipType) {\n return BetslipType.Combo;\n }\n\n if (type instanceof SystemSlipType) {\n return BetslipType.System;\n }\n\n return undefined;\n}\n\nexport function getEditBetSlipId(): string {\n return 'editbet';\n}\n\nexport function getComboSlipId(): string {\n return SlipTypeKeys.Combo;\n}\n\nexport function getSystemSlipId(slipType: SystemSlipType): string {\n return getSystemSlipIdFromString(slipType.key);\n}\n\nexport function getSystemSlipIdFromString(slipKey: string): string {\n return `${SYSTEM_SLIP_ID_PREFIX}${slipKey}`;\n}\n\nexport function isSystemSlipId(slipId: SlipId): boolean {\n return slipId.startsWith(SYSTEM_SLIP_ID_PREFIX);\n}\n\nexport function getSystemTypeKeyFromSlipId(slipId: SlipId): string | undefined {\n if (!isSystemSlipId(slipId)) {\n return undefined;\n }\n\n return slipId.substring(SYSTEM_SLIP_ID_PREFIX.length);\n}\n\nexport function isComboSlipId(slipId: SlipId): boolean {\n return slipId === getComboSlipId();\n}\n\nexport function getSingleSlipId(pickId: PickId): string {\n return pickId ? getSingleSlipIdFromString(pickId.toString()) : '';\n}\n\nexport function getSingleSlipIdFromString(pickId: string): string {\n return `${SINGLE_SLIP_ID_PREFIX}${pickId}`;\n}\n\nexport function getBetBuilderSlipId(pickId: PickId): string {\n return getBetBuilderSlipIdFromString(pickId.toString());\n}\n\nexport function getBetBuilderSlipIdFromString(pickId: string): string {\n return `${BET_BUILDER_SLIP_ID_PREFIX}${pickId}`;\n}\n\nexport function isSingleSlipId(slipId: SlipId): boolean {\n return slipId.startsWith(SINGLE_SLIP_ID_PREFIX);\n}\n\nexport function getTeaserSlipId(): string {\n return 'teaser';\n}\n\nexport function isTeaserSlipId(slipId: SlipId): boolean {\n return slipId === getTeaserSlipId();\n}\n\nexport function isBetBuilderSlipId(slipId: SlipId): boolean {\n return slipId.startsWith(BET_BUILDER_SLIP_ID_PREFIX);\n}\n\nexport function getBetslipTypeFromId(slipId: SlipId): BetslipType | null {\n if (isSingleSlipId(slipId)) {\n return BetslipType.Single;\n }\n\n if (isComboSlipId(slipId)) {\n return BetslipType.Combo;\n }\n\n if (isTeaserSlipId(slipId)) {\n return BetslipType.Teaser;\n }\n\n if (isBetBuilderSlipId(slipId)) {\n return BetslipType.BetBuilder;\n }\n\n if (isSystemSlipId(slipId)) {\n return BetslipType.System;\n }\n\n return null;\n}\n\n/**\n * Returns a unique list of picks accross multiple slips\n */\nexport function getUniqueSlipPicks(slips: ISlip[]): BetslipPick[] {\n const allPicks = slips.flatMap((slip) => Object.values(slip.slipPicks));\n\n return allPicks.reduce(\n (acc, curr, i) => {\n if (acc.some((pick) => pick.id === curr.pick.id)) {\n return acc;\n }\n\n return acc.concat([curr.pick]);\n },\n [],\n );\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { getLegsCount } from '../../core/utils';\nimport { betslipPicksListSelector } from '../picks/selectors';\nimport { betslipTypeStateSelector } from '../types/selectors';\nimport { BetBuilderPick, LinearBetBuilderPickId } from './models';\n\nexport const selectBetBuilderTypeState = createSelector(betslipTypeStateSelector, (betslipTypeState) => betslipTypeState.betBuilder);\n\nexport const selectBetBuilderTypeStatePicks = createSelector(selectBetBuilderTypeState, (betBuilderTypeState) => betBuilderTypeState.picks);\n\nexport const selectBetBuilderPicks = createSelector(\n selectBetBuilderTypeStatePicks,\n betslipPicksListSelector,\n (betBuilderPicks, pickList) => pickList.filter((pick) => !!betBuilderPicks[pick.id.toString()]) as BetBuilderPick[],\n);\n\nexport const selectBetBuilderPickStakeFactory = (pickId: LinearBetBuilderPickId) =>\n createSelector(selectBetBuilderTypeStatePicks, (picks) => {\n const pick = picks[pickId.toString()];\n\n if (!pick) {\n return {\n actualStake: null,\n stake: null,\n };\n }\n\n return {\n actualStake: pick.actualStake,\n stake: pick.stake,\n };\n });\n\nexport const selectBetBuilderPickLegsFactory = (pickId: LinearBetBuilderPickId) =>\n createSelector(betslipPicksListSelector, (pickList) => {\n const pick = pickList.find((p) => p.id.isEqual(pickId));\n\n return pick ? getLegsCount([pick]) : 0;\n });\n","import { createSelector } from '@ngrx/store';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { SlipId } from '../bet-generation/models';\nimport { getBetBuilderSlipIdFromString, getSingleSlipIdFromString, getSystemSlipIdFromString } from '../bet-generation/services/slip.utils';\nimport { betslipTypeStateSelector } from '../types/selectors';\nimport { IBetslipTypeState } from '../types/state';\n\nexport const selectLinearStakeForSlip = (slipId: SlipId, betslipType: BetslipType) =>\n createSelector(betslipTypeStateSelector, (types: IBetslipTypeState): string | null => {\n switch (betslipType) {\n case BetslipType.Combo:\n return types.comboBet.actualStake;\n\n case BetslipType.Teaser:\n return types.teaserBet.actualStake;\n\n case BetslipType.Single: {\n const pickEntry = Object.entries(types.singleBet.picks).find(([pickId]) => getSingleSlipIdFromString(pickId) === slipId);\n\n if (!pickEntry) {\n return null;\n }\n\n const [, pick] = pickEntry;\n\n return pick.actualStake ?? null;\n }\n\n case BetslipType.System: {\n const type = Object.keys(types.systemBet.linearTypeStakes).find((k) => getSystemSlipIdFromString(k) === slipId);\n\n if (!type) {\n return null;\n }\n\n const typeStake = types.systemBet.linearTypeStakes[type];\n\n return typeStake?.actualStake ?? null;\n }\n\n case BetslipType.EditBet: {\n return types.editBet.addedStake.toString();\n }\n\n case BetslipType.BetBuilder: {\n const pickEntry = Object.entries(types.betBuilder.picks).find(([pickId]) => getBetBuilderSlipIdFromString(pickId) === slipId);\n\n if (!pickEntry) {\n return null;\n }\n\n const [, pick] = pickEntry;\n\n return pick.actualStake ?? null;\n }\n }\n });\n","import { createSelector } from '@ngrx/store';\n\nimport { PickId } from '../../core/picks/pick-id';\nimport { PickOddsState } from '../../core/picks/pick-models';\nimport { betslipTypeStateSelector, selectSystemBetPicks } from '../types/selectors';\nimport { ISystemBetPickState, ISystemBetTypeStake } from './state';\n\nexport const systemBetStateSelector = createSelector(betslipTypeStateSelector, (state) => state.systemBet);\nexport const systemBetPicksSelector = createSelector(systemBetStateSelector, (s) => s.picks);\nexport const systemBetSystemInfoSelector = createSelector(systemBetStateSelector, (s) => s.systemInfo);\nexport const systemBetBankerEnabledSelector = createSelector(systemBetStateSelector, (s) => s.isBankerActive);\nexport const systemBetBankerSelector = createSelector(systemBetStateSelector, (s) => ({\n isBankerActive: s.isBankerActive,\n hasBanker: Object.values(s.picks).some((p) => p.isBanker),\n}));\nexport const systemBetBankerCount = createSelector(systemBetStateSelector, (s) =>\n s.isBankerActive ? Object.values(s.picks).filter((p) => p.isSelected && !p.isLocked && p.isBanker).length : 0,\n);\n\nexport const systemBetBarStateSelector = createSelector(systemBetStateSelector, (systemBetState) => {\n return { isBankerActive: systemBetState.isBankerActive, selectedSlipType: systemBetState.systemInfo?.key };\n});\n\nexport const systemBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(systemBetPicksSelector, (picks) => {\n return picks[pickId.toString()];\n });\n\nexport const systemBetTypeStakeSelectorFactory = (typeKey: string) =>\n createSelector(systemBetStateSelector, (state): ISystemBetTypeStake | null => {\n const typeStakes = state.linearTypeStakes ?? {};\n\n return typeStakes[typeKey] ?? null;\n });\n\nexport const systemBetLinearSlipTypeStakesSelector = createSelector(systemBetStateSelector, (s) => {\n return Object.values(s.linearTypeStakes).map((typeStake) => typeStake);\n});\n\nexport const systemBetPickOddStateSelector = createSelector(systemBetStateSelector, selectSystemBetPicks, (systemBetState, pickList) => {\n const picks: { [pickId: string]: ISystemBetPickState & { isOpen: boolean } } = {};\n\n pickList.forEach((pick) => {\n picks[pick.id.toString()] = {\n ...systemBetState.picks[pick.id.toString()],\n isOpen: pick.oddsState === PickOddsState.Open,\n };\n });\n\n return picks;\n});\n\nexport const systemBetStateSelectorPickOddStateSelector = createSelector(\n systemBetStateSelector,\n systemBetPickOddStateSelector,\n (systemBetState, picks) => {\n return { ...systemBetState, picks };\n },\n);\n\nexport const selectIsSystemBetExpanded = createSelector(systemBetStateSelector, (state) => state.isLinearExpanded);\n","import { BetslipError } from '../betslip-error';\n\nexport class MinSelectionsBetbuilderError extends BetslipError {\n constructor() {\n super();\n this.hasClientValidation = true;\n }\n}\n","import { BetPlacementError, BetPlacementErrorSaved } from '../bet-placement-error';\n\nexport interface ResultErrorSaved extends BetPlacementErrorSaved {\n pickId: string;\n}\n\nexport class ResultError extends BetPlacementError implements ResultErrorSaved {\n constructor(readonly pickId: string) {\n super();\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { BetslipPick } from '../../../../core/picks/betslip-pick';\nimport {\n getBetBuilderSlipIdFromString,\n getComboSlipId,\n getEditBetSlipId,\n getSingleSlipId,\n getSingleSlipIdFromString,\n getSystemSlipId,\n getTeaserSlipId,\n} from '../../../bet-generation/services/slip.utils';\nimport { IBetslipTypeState } from '../../../types/state';\nimport { BetslipError, ErrorOrigin } from '../../errors/betslip-error';\nimport { ResultError } from '../../errors/result/result-error';\nimport {\n BetslipTypeErrors,\n BetslipTypePickErrors,\n IBetslipErrorsState,\n IBetslipPickErrors,\n SlipErrors,\n emptyBetslipTypeErrors,\n emptyPickErrors,\n} from '../../state';\n\nexport function updateKeyBasedErrorCollection(\n currentCollection: Record,\n newCollection: Record,\n defaultCollection: Record,\n oldKeys: TKey[],\n newKeys: TKey[],\n): { errors: Record; hasChanged: boolean } {\n let hasChanged = false;\n\n const result = { ...defaultCollection };\n const clientTypeErrors = { ...defaultCollection };\n\n // Iterate through all keys (all slip types) for current errors\n for (const key of oldKeys) {\n const currentErrors = currentCollection[key];\n\n const clientResult = [];\n const serverResult = [];\n\n // Split into client and non-client errors\n for (const error of currentErrors) {\n if (error.hasClientValidation) {\n clientResult.push(error);\n\n hasChanged = hasChanged || !newKeys.includes(key);\n continue;\n }\n\n serverResult.push(error);\n }\n\n if (clientResult.length) {\n // Prepare client errors for comparison\n clientTypeErrors[key] = clientResult;\n }\n\n if (serverResult.length) {\n // Keep all non-client errors\n result[key] = serverResult;\n }\n }\n\n // Iterate through all keys (all slip types) for new errors\n for (const key of newKeys) {\n const clientErrors = clientTypeErrors[key];\n const newErrors = newCollection[key];\n\n // Make sure we keep result errors from before\n const existingResultErrors = result[key];\n\n if (!newErrors.length) {\n result[key] = existingResultErrors ? existingResultErrors : [];\n hasChanged = hasChanged || !oldKeys.includes(key) || !!clientErrors?.length;\n\n continue;\n }\n\n // Check if we have current errors for this key\n if (!clientErrors || !clientErrors.length) {\n // If we don't have current errors, just take the new ones\n result[key] = existingResultErrors ? [...existingResultErrors, ...newErrors] : newErrors;\n hasChanged = true;\n\n continue;\n }\n\n // If we have current errors for this slip, update the errors\n const resultErrors = errorCollectionUpdater(clientErrors, newErrors);\n\n result[key] = existingResultErrors ? [...existingResultErrors, ...resultErrors] : resultErrors;\n\n // If the errors have changed before or the result errors do not equal the current erros, set hasChanged to true\n hasChanged = hasChanged || resultErrors !== clientErrors;\n }\n\n return {\n errors: result,\n hasChanged,\n };\n}\n\n/**\n * A betslip errors reducer helper. Add/Replace/Deletes errors from current errors for which we have a validator\n *\n * @param currentErrors\n * @param newErrors - It is mandatory all new errors to be with hasClientValidation === true.\n */\nexport const errorCollectionUpdater = (currentErrors: TError[], newErrors: TError[]): TError[] => {\n // Get all errors that are not validated on client.\n const result: TError[] = [];\n const clientErrors: TError[] = [];\n for (const error of currentErrors) {\n if (error.hasClientValidation) {\n clientErrors.push(error);\n } else {\n result.push(error);\n }\n }\n\n let hasNewError = false;\n\n // Go foreach of new errors.\n for (const newError of newErrors) {\n // Try to check if already this error exists.\n const sameError = clientErrors.find((be) => be.equals(newError));\n\n // If error is already present do not update it.\n if (sameError) {\n // Take the same error just change the origin.\n const updatedError = sameError.copy();\n updatedError.origin = ErrorOrigin.BetSlip;\n result.push(updatedError);\n } else {\n hasNewError = true;\n result.push(newError);\n }\n }\n if (result.length === currentErrors.length && !hasNewError) {\n return currentErrors;\n }\n\n return result;\n};\n\nexport function extractPickErrors(\n pickErrors: BetslipTypePickErrors,\n betslipTypeErrors: BetslipTypeErrors,\n): [BetslipTypePickErrors, BetslipTypeErrors] {\n const extractedPickErrors: BetslipTypePickErrors = {\n SINGLE: { ...pickErrors[BetslipType.Single] },\n COMBO: { ...pickErrors[BetslipType.Combo] },\n SYSTEM: { ...pickErrors[BetslipType.System] },\n TEASER: { ...pickErrors[BetslipType.Teaser] },\n EDIT_MY_BET: { ...pickErrors[BetslipType.EditBet] },\n BET_BUILDER: { ...pickErrors[BetslipType.BetBuilder] },\n };\n\n const filteredTypeErrors: BetslipTypeErrors = emptyBetslipTypeErrors();\n\n for (const type of [BetslipType.Single, BetslipType.Combo, BetslipType.System, BetslipType.Teaser, BetslipType.EditBet, BetslipType.BetBuilder]) {\n const typeErrors = betslipTypeErrors[type];\n const typePickErrors = extractedPickErrors[type];\n\n for (const error of typeErrors) {\n if (error instanceof ResultError && !!error.pickId) {\n typePickErrors[error.pickId] = typePickErrors[error.pickId] || [];\n\n typePickErrors[error.pickId].push(error);\n continue;\n }\n\n filteredTypeErrors[type].push(error);\n }\n }\n\n return [extractedPickErrors, filteredTypeErrors];\n}\n\nexport const getTypedPickErrors = function (pickErrors: BetslipTypePickErrors): [BetslipType, IBetslipPickErrors][] {\n return Object.entries(pickErrors).map(([key, errors]) => [key as BetslipType, errors]);\n};\n\nconst getTypedBetslipErrors = function (slipErrors: BetslipTypeErrors): [BetslipType, BetslipError[]][] {\n return Object.entries(slipErrors).map(([key, errors]) => [key as BetslipType, errors]);\n};\n\nexport const filterErrorState = (\n state: IBetslipErrorsState,\n betslipErrorsPredicate: (error: BetslipError) => boolean,\n betslipTypeErrorsPredicate: (error: BetslipError) => boolean,\n slipErrorsPredicate: (error: BetslipError) => boolean,\n pickErrorsPredicate: (error: ResultError) => boolean,\n): IBetslipErrorsState => {\n const betslipErrors = state.betslipErrors.filter(betslipErrorsPredicate);\n\n const pickErrors: BetslipTypePickErrors = emptyPickErrors();\n const betslipTypeErrors: BetslipTypeErrors = emptyBetslipTypeErrors();\n const slipErrors: Record = {};\n\n for (const [type, errors] of getTypedPickErrors(state.pickErrors)) {\n const pickIds = Object.keys(errors);\n\n for (const pickId of pickIds) {\n pickErrors[type][pickId] = errors[pickId].filter(pickErrorsPredicate);\n }\n }\n\n for (const [type, errors] of getTypedBetslipErrors(state.betslipTypeErrors)) {\n betslipTypeErrors[type] = errors.filter(betslipTypeErrorsPredicate);\n }\n\n for (const [key, errors] of Object.entries(state.slipErrors)) {\n slipErrors[key] = errors.filter(slipErrorsPredicate);\n }\n\n return {\n pickErrors,\n betslipTypeErrors,\n betslipErrors,\n slipErrors,\n };\n};\n\n// Caution: This modifies the original errors which is fine for reducers since they handle immutability.\n// This should be used with caution elsewhere.\nexport const modifyErrorState = (\n state: IBetslipErrorsState,\n betslipErrorsMap: (error: BetslipError) => [BetslipError, boolean],\n betslipTypeErrorsMap: (error: BetslipError) => [BetslipError, boolean],\n slipErrorsMap: (error: BetslipError) => [BetslipError, boolean],\n pickErrorsMap: (error: ResultError) => [ResultError, boolean],\n): IBetslipErrorsState & { isModified: boolean } => {\n const betslipErrorsMapping = state.betslipErrors.map(betslipErrorsMap);\n\n let isModified = betslipErrorsMapping.some(([_, modified]) => modified);\n\n const betslipErrors = betslipErrorsMapping.map(([e]) => e);\n\n const pickErrors: BetslipTypePickErrors = emptyPickErrors();\n const betslipTypeErrors: BetslipTypeErrors = emptyBetslipTypeErrors();\n const slipErrors: Record = {};\n\n for (const [type, errors] of getTypedPickErrors(state.pickErrors)) {\n const pickIds = Object.keys(errors);\n\n for (const pickId of pickIds) {\n const mappedErrors = errors[pickId].map(pickErrorsMap);\n\n isModified = isModified || mappedErrors.some(([_, modified]) => modified);\n\n pickErrors[type][pickId] = mappedErrors.map(([e]) => e);\n }\n }\n\n for (const [type, errors] of getTypedBetslipErrors(state.betslipTypeErrors)) {\n const mappedErrors = errors.map(betslipTypeErrorsMap);\n\n isModified = isModified || mappedErrors.some(([_, modified]) => modified);\n\n betslipTypeErrors[type] = mappedErrors.map(([e]) => e);\n }\n\n for (const [key, errors] of Object.entries(state.slipErrors)) {\n const mappedErrors = errors.map(slipErrorsMap);\n\n isModified = isModified || mappedErrors.some(([_, modified]) => modified);\n\n slipErrors[key] = mappedErrors.map(([e]) => e);\n }\n\n return {\n pickErrors,\n betslipTypeErrors,\n betslipErrors,\n slipErrors,\n isModified,\n };\n};\n\nexport function getSlipErrorsForType(type: BetslipType, slipErrors: SlipErrors, typesState: IBetslipTypeState, isLinear: boolean): BetslipError[] {\n switch (type) {\n case BetslipType.Combo:\n return slipErrors[getComboSlipId()] ?? [];\n\n case BetslipType.Teaser:\n return slipErrors[getTeaserSlipId()] ?? [];\n\n case BetslipType.System:\n return isLinear\n ? Object.values(typesState.systemBet.linearTypeStakes).flatMap((systemType) => slipErrors[getSystemSlipId(systemType.slipType)] ?? [])\n : typesState.systemBet.systemInfo\n ? (slipErrors[getSystemSlipId(typesState.systemBet.systemInfo)] ?? [])\n : [];\n\n case BetslipType.Single:\n return Object.keys(typesState.singleBet.picks).flatMap((pick) => slipErrors[getSingleSlipIdFromString(pick)] ?? []);\n\n case BetslipType.EditBet:\n return slipErrors[getEditBetSlipId()] ?? [];\n\n case BetslipType.BetBuilder:\n return Object.keys(typesState.betBuilder.picks).flatMap((pick) => slipErrors[getBetBuilderSlipIdFromString(pick)] ?? []);\n }\n}\n\nexport function checkSlipErrorsForSinglePicks(slipErrors: SlipErrors, picks: BetslipPick[], predicate: (error: BetslipError) => boolean): boolean {\n return picks.some((pick) => slipErrors[getSingleSlipId(pick.id)]?.some(predicate));\n}\n\nexport function hasOddsChangedError(pickErrors: BetslipTypePickErrors): boolean {\n return Object.values(pickErrors).some((p) =>\n Object.values(p).some((errors) => errors.some((error) => error.type === PlacementErrorType.OddsChanged)),\n );\n}\n\nexport const getAllPickErrorTypes = function (typePickErrors: BetslipTypePickErrors): Set {\n const types = Object.values(typePickErrors).flatMap((typeErrors) =>\n Object.values(typeErrors).flatMap((errors) => errors.map((error) => error.type)),\n );\n\n return new Set(types);\n};\n\nexport const hasAnyErrors = function (state: IBetslipErrorsState): boolean {\n return (\n state.betslipErrors.length > 0 ||\n Object.values(state.slipErrors).some((s) => s.length > 0) ||\n Object.values(state.betslipTypeErrors).some((s) => s.length > 0) ||\n Object.values(state.pickErrors).some((p) => Object.values(p).some((e) => e.length > 0))\n );\n};\n\nexport const getErrorsAcrossTypesForPicks = function (errors: BetslipTypePickErrors): IBetslipPickErrors {\n return Object.values(errors).reduce((a, c) => {\n const pickErrors = Object.entries(c);\n\n for (const [key, value] of pickErrors) {\n a[key] = a[key] ? [...a[key], ...value] : [...value];\n }\n\n return a;\n }, {} as IBetslipPickErrors);\n};\n","import { BetSlipType, OddsBoostFormula } from '@bpos';\nimport { ReplaceableParameter } from '@bpos/v1/sports-promo/campaigns';\nimport { RewardTargetType } from '@bpos/v1/sports-promo/tokens';\nimport { NumberDictionary, StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport { Odds } from '@frontend/sports/odds/feature';\nimport {\n BetStatus,\n BuildABet,\n PayoutType,\n RewardTokenType,\n RewardTriggerType,\n TokenFilter,\n} from 'packages/sports/web/app/src/tokens-base/token-base.models';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { IRewardTokenEligibility } from './state';\n\nexport const ACCA_BOOST_ID = 'AccaBoost';\n\nexport interface IRewardToken {\n id: string;\n startDate: Date;\n expiryDate: Date;\n cmsItemId: string;\n userTokenId: string;\n minimumLegs?: number;\n wageringMultiplier?: number;\n payoutType: PayoutType;\n tokenType: string;\n filter: TokenFilter;\n rewardTokenType: RewardTokenType;\n payoutExpiry?: number;\n minOdds?: number;\n customTitle?: string;\n maximumStake?: number;\n selectionLevelMinimumOdds?: TokenOddsData;\n selectionLevelMaximumOdds?: TokenOddsData;\n replaceableParameters?: StringDictionary;\n minimumOdds?: TokenOddsData;\n maximumOdds?: TokenOddsData;\n customDescription?: string;\n isBetBuilder?: boolean;\n isSingleAndComboSelected?: boolean;\n url?: string;\n customMarketTypes?: string;\n rewardTargetType?: RewardTargetType;\n isIncrementalPayout?: boolean;\n incrementalBetAmount?: number;\n incrementalPayoutNoOfTimes?: number;\n isVipExclusive?: boolean;\n}\n\nexport interface OddsBoostToken extends IRewardToken {\n boostFactor: number;\n maxWinningsBoost?: number;\n minimumStake?: number;\n oddsBoostFormula: OddsBoostFormula;\n}\n\nexport interface RiskFreeToken extends IRewardToken {\n maximumPayout: number;\n minimumStake?: number;\n riskFreePercentage: number;\n}\n\nexport interface FreeBetToken extends IRewardToken {\n amount: number;\n}\n\nexport interface BetAndGetToken extends IRewardToken {\n percentagePayout: number | undefined;\n payoutValue: number;\n rewardTriggerType: RewardTriggerType;\n maximumPayout?: number;\n minimumPayout?: number;\n betStatus: BetStatus;\n minimumStake: number;\n subType: string;\n isEarlyPayoutEnabled?: boolean;\n buildABet?: BuildABet;\n}\n\nexport interface TokenOddsData {\n european: number;\n britishNumerator: number;\n britishDenominator: number;\n american: number;\n}\n\nexport interface BetStationFreeBetToken extends IRewardToken {\n amount: number;\n currency: string;\n marketTemplateIds: Array;\n marketParameters: Array;\n}\n\nexport interface AccaBoostToken extends IRewardToken {\n boostLadder: AccaBoostLadderItem[];\n maxReward: number;\n priority: number;\n filter: AccaBoostFilter;\n countryCodes: string[];\n}\nexport interface EdsToken extends IRewardToken {\n priority?: number;\n replaceableParameters: Record;\n filter: EdsFilter;\n}\n\nexport interface AccaBoostLadderItem {\n numberOfLegs: number;\n ratio: number;\n}\nexport interface EdsFilter extends TokenFilter {\n marketIds: Array;\n minSelections?: number;\n maxSelections?: number;\n minStake?: number;\n maxStake?: number;\n currency?: string;\n overallMinOdds?: Odds;\n overallMaxOdds?: Odds;\n selectionMinOdds?: Odds;\n selectionMaxOdds?: Odds;\n}\n\nexport interface AccaBoostFilter extends TokenFilter {\n sportsConfigs: Record;\n minOddsPerLeg: AccaBoostTokenMinOdds;\n}\n\nexport interface AccaBoostSportConfig {\n gridGroups: StringDictionary;\n competitions: StringDictionary;\n}\n\nexport interface AccaBoostTokenMinOdds {\n european: number;\n britishNumerator: number;\n britishDenominator: number;\n american: number;\n}\n\nexport interface BestOddsGuaranteedToken extends IRewardToken {\n filter: BestOddsGuaranteedFilter;\n}\n\nexport interface BestOddsGuaranteedFilter extends TokenFilter {\n slipTypes: BetSlipType[];\n sportsFilter: NumberDictionary;\n}\n\nexport interface BestOddsGuaranteedSportsFilter {\n regions: number[];\n competitions: number[];\n meetings: number[];\n}\n\nexport enum AvailableTokensType {\n FreeBet = 'FreeBet',\n RiskFreeBet = 'RiskFreeBet',\n OddsBoost = 'OddsBoost',\n BetAndGet = 'BetAndGet',\n AccaBoost = 'AccaBoost',\n MultipleRewards = 'MultipleRewards',\n}\n\nexport enum CriteriaType {\n Sports = 'Sports',\n Competitions = 'Competitions',\n Meetings = 'Meetings',\n Fixtures = 'Fixtures',\n GameType = 'GameType',\n OptionId = 'OptionId',\n TemplateId = 'TemplateId',\n MarketParameter = 'MarketParameter',\n MarketId = 'MarketId',\n Forbidden = 'Forbidden',\n EachWaySelected = 'EachWaySelected',\n StartDate = 'StartDate',\n ExpiryDate = 'ExpiryDate',\n ParticipantBetSelected = 'ParticipantBetSelected',\n BetBuilderBetSelected = 'BetBuilderBetSelected',\n MinOdds = 'MinOdds',\n MaxOdds = 'MaxOdds',\n SelectionMaxOdds = 'SelectionMaxOdds',\n Market = 'Market',\n // eslint-disable-next-line @typescript-eslint/no-shadow\n SlipType = 'SlipType',\n MinimumLegs = 'MinimumLegs',\n MaximumStake = 'MaximumStake',\n MinimumStake = 'MinimumStake',\n DayOfWeek = 'DayOfWeek',\n TimeOfDay = 'TimeOfDay',\n BestOddsFlag = 'BestOddsFlag',\n MultiSingleBet = 'MultiSingleBet',\n SelectionOdds = 'SelectionOdds',\n TotalOdds = 'TotalOdds',\n SelectionsCount = 'SelectionsCount',\n Stake = 'Stake',\n Currency = 'Currency',\n BetBuilder = 'BetBuilder',\n SystemBet = 'SystemBet',\n TeaserReward = 'TeaserReward',\n EachWay = 'EachWay',\n ComboPrevention = 'ComboPrevention',\n}\n\nexport interface IOnboardingViewData {\n title: string;\n content: string;\n icon: string | undefined;\n isNew: boolean;\n}\n\nexport interface IRewardTokenSelection {\n tokenId: string;\n pickId?: PickId;\n betslipType?: BetslipType;\n trackingSource?: { [key: string]: string };\n}\n\nexport interface IRewardTokenDeSelection {\n rewardTokenType?: RewardTokenType;\n}\n\nexport interface IRewardTokenInfo {\n token: IRewardToken;\n selected: boolean;\n}\n\nexport interface MarketParameter {\n marketType: string;\n period: string;\n happening: string;\n}\n\nexport enum RewardBannerTrackingConstants {\n Load = 'load',\n Expand = 'expand',\n Collapse = 'collapse',\n Click = 'click',\n BetRewardsBanner = 'bet rewards banner',\n RewardsBanner = 'rewards banner',\n RewardDetails = 'reward details',\n PromoHub = 'promo hub',\n}\n\nexport interface BetAndGetEdsToken {\n payoutType: PayoutType;\n triggerType: RewardTriggerType;\n payoutDetails: PayoutDetails;\n maximumPayout?: number;\n rewardTargetType?: RewardTargetType;\n}\n\nexport interface PayoutDetails extends IncrementalPayoutDetails {\n value: number;\n percentage: number | undefined;\n maxValue?: number;\n}\n\nexport interface VIPExclusiveDetails {\n badge: string;\n badgeSize: string;\n badgeTitle: string;\n}\n\nexport interface PromoTokenPlaceBetSignPostingInfo extends IncrementalPayoutDetails {\n rewardTargetType?: RewardTargetType;\n}\n\nexport interface IncrementalPayoutDetails {\n noOfSlabs?: number;\n slabAmount?: number;\n}\n\n// TODO - nx library split - During consolidation of the tokens models we should check if we can take these values directly from the bpos contract\n// TODO: @Andriy explore ways of having this from bpos-v4 typings\nexport enum RewardTokenAdditionalInfoKeys {\n BoostPercentage = 'boostPercentage',\n MaxTokenStake = 'maxTokenStake',\n MaxWinningsBoost = 'maxWinningsBoost',\n UserExpectedWinningsBoost = 'userExpectedWinningsBoost',\n RiskFreeMaxCompensation = 'riskFreeMaxCompensation',\n RiskFreePercentage = 'riskFreePercentage',\n AccaBoostRatio = 'accaBoostRatio',\n AccaBoostFormulaType = 'formulaType',\n EdsCampaignPromotionType = 'edsCampaignPromotionType',\n BetIndex = 'betIndex',\n IsDigitalV2 = 'isDigitalV2',\n OddsBoostFormulaType = 'oddsBoostFormula',\n PlaceBetSignPostingData = 'placeBetSignPostingData',\n PlaceBetSignPostingVersion = 'placeBetSignPostingVersion',\n}\n\nexport interface EdsTokenWithEligiblePicks {\n token: EdsToken;\n eligiblePicksIds: string[];\n}\n\nexport interface BogTokenWithEligibility {\n token: BestOddsGuaranteedToken;\n eligibility: IRewardTokenEligibility | undefined;\n}\n\nexport interface RewardTokenContext {\n betslipType: BetslipType | undefined;\n pickId?: PickId;\n}\n\nexport interface RewardTokenSelectionContext {\n betslipType: BetslipType | undefined;\n pickId?: string;\n}\n\nexport interface SelectedRewardTokenState {\n tokenId: string;\n token: IRewardToken | null;\n tokenEligibility: IRewardTokenEligibility | null;\n}\n\nexport interface AcquisitionContext {\n acquisitionNotificationShown: boolean;\n autoApplyNotificationShown: boolean;\n AddWelcomeOfferNotificationShown: boolean;\n deselectedByUser: boolean;\n rewardAutoApplied: boolean;\n}\n\nexport enum AcquisitionContextType {\n BannerTooltip = 'BannerTooltip',\n AutoApplyTooltip = 'AutoApplyTooltip',\n AutoApply = 'AutoApply',\n DeselectedByUser = 'DeselectedByUser',\n AddWelcomeOffer = 'AddWelcomeOffer',\n}\n","import { EdsPromoTokenType } from '@bpos';\nimport { EdsPromotionToken } from '@bpos/v1/my-bets';\nimport { RewardTargetType } from '@bpos/v1/sports-promo/tokens';\nimport { FixturePricingState } from '@cds/betting-offer/add-ons';\nimport { Nullable, hasValue, isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { CalculatedOdds, Odds, OddsOperations, emptyCalculatedOdds, emptyOdds } from '@frontend/sports/odds/feature';\nimport { Decimal } from 'decimal.js';\nimport { orderBy } from 'lodash-es';\nimport { BestOddsFixtureLikeInput } from 'packages/sports/web/app/src/best-odds-guarantee/models/models';\nimport { BuildABet, IRewardTokenData, PayoutType, RewardTokenType } from 'packages/sports/web/app/src/tokens-base/token-base.models';\n\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetBuilderPickId, GroupPickId } from '../../../core/picks/pick-id';\nimport { PickOddsState, PriceType } from '../../../core/picks/pick-models';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\nimport { filterPicksForType } from '../../picks/services/linear-betslip-pick.utils';\nimport { ISlipSummary } from '../../summary/models';\nimport { IBetslipTypeState } from '../../types/state';\nimport { isErrorComboPrevention } from '../../validation/services/utils/betslip-errors-utils';\nimport { BetslipTypePickErrors } from '../../validation/state';\nimport {\n AccaBoostLadderItem,\n AccaBoostToken,\n AvailableTokensType,\n BetAndGetToken,\n CriteriaType,\n FreeBetToken,\n IRewardToken,\n OddsBoostToken,\n RewardTokenContext,\n RiskFreeToken,\n TokenOddsData,\n} from '../reward-tokens.model';\nimport { IRewardTokenEligibility, IRewardTokenEligibilityState, TokensRecord } from '../state';\n\nexport function isFreebetToken(token?: IRewardToken | IRewardTokenData | null): token is FreeBetToken {\n return !!token && token.rewardTokenType === RewardTokenType.FreeBet;\n}\n\nexport function isOddsBoostToken(token: IRewardToken | IRewardTokenData | null): token is OddsBoostToken {\n return !!token && token.rewardTokenType === RewardTokenType.OddsBoost;\n}\n\nexport function isRiskFreeToken(token: IRewardToken | IRewardTokenData | null): token is RiskFreeToken {\n return !!token && token.rewardTokenType === RewardTokenType.RiskFreeBet;\n}\n\nexport function isBetAndGetToken(token: IRewardToken | IRewardTokenData | null): token is BetAndGetToken {\n return !!token && token.rewardTokenType === RewardTokenType.BetAndGet;\n}\n\nexport function isAccaBoostToken(token?: Nullable): token is AccaBoostToken {\n return !!token && token.rewardTokenType === RewardTokenType.AccaBoost;\n}\n\nexport function getAcquisitionRewardToken(tokens: IRewardToken[]): IRewardToken | undefined {\n return tokens.find((token) => isAcquisitionRewardToken(token));\n}\n\nconst isAcquisitionRewardToken = (token: IRewardToken): boolean => token.rewardTargetType === RewardTargetType.WelcomeOffer;\n\nexport function getPayoutExpiryInDays(token: IRewardToken): string {\n if (!token.payoutExpiry) {\n return '1';\n }\n\n const expiry = token.payoutExpiry / 24;\n\n return (expiry <= 1 ? 1 : Math.round(expiry)).toString();\n}\n\nexport function getTokensType(tokens: IRewardToken[]): AvailableTokensType {\n if (tokens.every((token) => isFreebetToken(token))) {\n return AvailableTokensType.FreeBet;\n }\n if (tokens.every((token) => isOddsBoostToken(token))) {\n return AvailableTokensType.OddsBoost;\n }\n if (tokens.every((token) => isRiskFreeToken(token))) {\n return AvailableTokensType.RiskFreeBet;\n }\n if (tokens.every((token) => isAccaBoostToken(token))) {\n return AvailableTokensType.AccaBoost;\n }\n\n if (tokens.every((token) => isBetAndGetToken(token))) {\n return AvailableTokensType.BetAndGet;\n }\n\n return AvailableTokensType.MultipleRewards;\n}\n\nexport function getNonAccaTokens(tokens: IRewardToken[]): IRewardToken[] {\n return tokens.filter((token) => !isAccaBoostToken(token));\n}\n\nexport function getSortedNonAccaTokensWithExpiryDate(tokens: IRewardToken[], isVIPBadgeEnabled: boolean): IRewardToken[] {\n return orderBy(getNonAccaTokens(tokens), [\n (token) => token.rewardTargetType !== RewardTargetType.WelcomeOffer,\n (token) => (isVIPBadgeEnabled ? !token.isVipExclusive && token.expiryDate : false),\n (token) => token.expiryDate,\n ]);\n}\n\nexport function getSortedNonAccaTokens(tokens: IRewardToken[]): IRewardToken[] {\n return orderBy(getNonAccaTokens(tokens), (token) => isAcquisitionRewardToken(token), 'desc');\n}\n\nexport function findAccaBoostLadderItem(token: AccaBoostToken, count: number): AccaBoostLadderItem {\n return token.boostLadder.reduce((previous, current) => {\n return current.numberOfLegs <= count ? current : previous;\n });\n}\n\nexport function getAccaBoostRatio(token: AccaBoostToken, count: number): Decimal {\n return new Decimal(findAccaBoostLadderItem(token, count).ratio);\n}\n\nexport function findMinimumLegsAccaTokens(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): AccaBoostToken[] {\n const accaTokens = Object.values(tokens).filter(isAccaBoostToken);\n\n return accaTokens.sort(byAccaPriority).filter((token) => {\n const eligibility = eligibilityState.comboBet.find((item) => item.tokenId === token.userTokenId);\n\n return !!eligibility && eligibility.invalidHardCriteria === CriteriaType.MinimumLegs;\n });\n}\n\nexport function findMinimumOddsAccaTokens(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): AccaBoostToken | undefined {\n const accaTokens = Object.values(tokens).filter(isAccaBoostToken);\n\n return accaTokens.sort(byAccaPriority).find((token) => {\n const eligibility = eligibilityState.comboBet.find((item) => item.tokenId === token.userTokenId);\n\n return !!eligibility && eligibility.invalidHardCriteria === CriteriaType.MinOdds;\n });\n}\n\nexport const byAccaPriority = (first: AccaBoostToken, second: AccaBoostToken): number => first.priority - second.priority;\n\nexport function isValidAccaBoostCombo(\n typePickErrors: BetslipTypePickErrors,\n pickList: BetslipPick[],\n types: IBetslipTypeState,\n isLinear: boolean,\n): boolean {\n const type = types.base.currentSelectedType;\n\n const isValidTab = (type === BetslipType.Combo && !types.comboBet.isEachWay) || type === BetslipType.Single;\n\n if (!type || !isValidTab) {\n return false;\n }\n\n const selectedPicks = filterPicksForType(types, type, pickList, {\n isLocked: isLinear ? false : undefined,\n isSelected: true,\n });\n\n if (selectedPicks.length < 2) {\n return false;\n }\n\n const allValidPicks = selectedPicks.every(({ oddsState }) => oddsState === PickOddsState.Open);\n\n if (!allValidPicks) {\n return false;\n }\n\n const pickErrors = typePickErrors[type];\n\n return !Object.values(pickErrors)\n .flatMap((errors) => errors)\n .some((e) => isErrorComboPrevention(e));\n}\n\nexport function calcTotalOdds(picks: BetslipPick[], roundPriceValues: boolean = false): CalculatedOdds {\n const totalOdds = picks.reduce(\n (odds: CalculatedOdds, pick: BetslipPick) => {\n if (\n (OddsOperations.isOddsEmpty(odds),\n !pick.currentPrice || pick.priceType !== PriceType.Fixed || OddsOperations.isOddsEmpty(pick.currentPrice.nativeOdds))\n ) {\n odds = emptyCalculatedOdds;\n } else {\n odds = OddsOperations.multiply([odds, OddsOperations.toCalculatedOdds(pick.currentPrice!.nativeOdds)], roundPriceValues);\n }\n\n return odds;\n },\n OddsOperations.fromDecimalValue(new Decimal(1)),\n );\n\n return totalOdds;\n}\n\nexport function calcTotalOddsForOddsBoost(picks: BetslipPick[], roundPriceValues: boolean = false): CalculatedOdds {\n const oddsArray = picks.filter((p) => p.currentPrice?.nativeOdds).map((p) => OddsOperations.toCalculatedOdds(p.currentPrice!.nativeOdds));\n\n return OddsOperations.multiply(oddsArray, roundPriceValues);\n}\n\nexport function calculateWinningsBoost(slipSummary: ISlipSummary): Decimal {\n const boostedWinnings = slipSummary.rewardTokenModifications?.oddsBoost?.boostedWinnings;\n const possibleWinnings = slipSummary.possibleWinnings;\n\n return boostedWinnings && possibleWinnings ? boostedWinnings.minus(possibleWinnings) : new Decimal(0);\n}\n\nexport function convertToBestOddsFixtureLikeInput(\n pick: BetslipV2HorseRaceOptionMarketPick | BetslipV2HorseRaceWinParticipantPick | BetslipV2HorseRaceXCastPick,\n): BestOddsFixtureLikeInput {\n const horseFixture = pick.fixture;\n const fixture: BestOddsFixtureLikeInput = {\n startDate: pick.fixture.eventDate.toISOString(),\n fixtureType: pick.fixture.fixtureType,\n sport: { id: horseFixture.sportId },\n meeting: hasValue(horseFixture.league) ? { id: horseFixture.league.id } : undefined,\n region: hasValue(horseFixture.region) ? { id: horseFixture.region.id } : undefined,\n competition: hasValue(horseFixture.league) ? { id: horseFixture.league.id } : undefined,\n addons: {\n pricingState: FixturePricingState.None,\n isRaceOff: horseFixture.isRaceOff,\n bestOddsGuarantee:\n horseFixture.bestOddsGuarantee && pick.currentPrice?.type !== PriceType.StartingPrice && !BetslipV2HorseRaceXCastPick.isPick(pick),\n },\n };\n\n return fixture;\n}\n\nexport function getAllEligibleTokens(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): IRewardToken[] {\n const ids: string[] = [];\n\n Object.values(eligibilityState.singleBet)\n .flatMap((e) => e)\n .forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n Object.values(eligibilityState.betBuilder)\n .flatMap((e) => e)\n .forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n eligibilityState.comboBet.forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n eligibilityState.teaserBet.forEach((eligibility) => {\n if (!eligibility.isEligible || ids.includes(eligibility.tokenId)) {\n return;\n }\n\n ids.push(eligibility.tokenId);\n });\n\n return ids.map((id) => tokens[id]).filter(isDefined);\n}\n\nexport function isBetBuilderExclusiveToken(token: IRewardToken | BetAndGetToken) {\n return (\n (token.isBetBuilder && !token.filter.betSlipType && !token.isSingleAndComboSelected) ||\n ('buildABet' in token && token.buildABet === BuildABet.Yes)\n );\n}\nexport function isBetBuilderToken(token: IRewardToken | BetAndGetToken) {\n return token.isBetBuilder || ('buildABet' in token && token?.buildABet !== BuildABet.No);\n}\n\n// This is only used for quick-bet-builder-drawer, therefore we do not need\nexport function getTokenContextForBetBuilderPickId(\n pickId: BetBuilderPickId | GroupPickId,\n isSportcastAsComboEnabled: boolean,\n isLinear: boolean,\n): RewardTokenContext {\n if (isLinear) {\n return {\n betslipType: BetslipType.BetBuilder,\n pickId,\n };\n }\n\n const isComboBetContext = GroupPickId.isId(pickId) || isSportcastAsComboEnabled;\n\n return {\n betslipType: isComboBetContext ? BetslipType.Combo : BetslipType.Single,\n pickId: isComboBetContext ? undefined : pickId,\n };\n}\n\nexport function mapOdds(oddsPerLeg: TokenOddsData | undefined): Odds {\n if (!oddsPerLeg) {\n return emptyOdds;\n }\n\n const { european, britishNumerator, britishDenominator, american } = oddsPerLeg;\n\n return OddsOperations.createOdds({\n odds: european,\n numerator: britishNumerator,\n denominator: britishDenominator,\n americanOdds: american,\n });\n}\n\nexport function parseMarketType(token: IRewardToken): string[] {\n const marketTypes = token.customMarketTypes\n ? (token.customMarketTypes.split(',') ?? [])\n : token.filter.marketParameters.map((mt) => mt.marketType);\n\n return marketTypes;\n}\n\nexport function isIncrementalPayoutEligible(token: IRewardToken, stake?: number): boolean {\n return (\n token.payoutType === PayoutType.FreeBet &&\n !!token.isIncrementalPayout &&\n !!token.incrementalBetAmount &&\n !!token.incrementalPayoutNoOfTimes &&\n !!stake &&\n stake >= token.incrementalBetAmount &&\n token.incrementalPayoutNoOfTimes > 1\n );\n}\n\nexport function isEdsPromoTokenBetAndGet(edsPromoTokens: EdsPromotionToken[] | undefined): boolean {\n return edsPromoTokens?.length === 1 && edsPromoTokens[0].tokenType === EdsPromoTokenType.BetAndGet;\n}\n\nexport function isAcquisitionEligible(token: IRewardTokenEligibility, acquisitionUserTokenId: string): boolean {\n return token.isEligible && !!Object.values(token.softCriteriasValidity).every(Boolean) && acquisitionUserTokenId === token.tokenId;\n}\n","export const SYSTEMS = [\n { id: 'systembankersingles', picks: -1, bets: -1, group: 1, basis: [1] },\n { id: 'system2of3', picks: 3, bets: 3, group: 2, basis: [2] },\n { id: 'patent', picks: 3, bets: 7, group: 9, basis: [1, 2, 3] },\n { id: 'trixie', picks: 3, bets: 4, group: 8, basis: [2, 3] },\n { id: 'system3of4', picks: 4, bets: 4, group: 3, basis: [3] },\n { id: 'system2of4', picks: 4, bets: 6, group: 2, basis: [2] },\n { id: 'lucky15', picks: 4, bets: 15, group: 9, basis: [1, 2, 3, 4] },\n { id: 'yankee', picks: 4, bets: 11, group: 8, basis: [2, 3, 4] },\n { id: 'system4of5', picks: 5, bets: 5, group: 4, basis: [4] },\n { id: 'system3of5', picks: 5, bets: 10, group: 3, basis: [3] },\n { id: 'system2of5', picks: 5, bets: 10, group: 2, basis: [2] },\n { id: 'lucky31', picks: 5, bets: 31, group: 9, basis: [1, 2, 3, 4, 5] },\n { id: 'canadian', picks: 5, bets: 26, group: 8, basis: [2, 3, 4, 5] },\n { id: 'system5of6', picks: 6, bets: 6, group: 5, basis: [5] },\n { id: 'system4of6', picks: 6, bets: 15, group: 4, basis: [4] },\n { id: 'system3of6', picks: 6, bets: 20, group: 3, basis: [3] },\n { id: 'system2of6', picks: 6, bets: 15, group: 2, basis: [2] },\n { id: 'lucky63', picks: 6, bets: 63, group: 9, basis: [1, 2, 3, 4, 5, 6] },\n { id: 'heinz', picks: 6, bets: 57, group: 8, basis: [2, 3, 4, 5, 6] },\n { id: 'system6of7', picks: 7, bets: 7, group: 6, basis: [6] },\n { id: 'system5of7', picks: 7, bets: 21, group: 5, basis: [5] },\n { id: 'system4of7', picks: 7, bets: 35, group: 4, basis: [4] },\n { id: 'system3of7', picks: 7, bets: 35, group: 3, basis: [3] },\n { id: 'system2of7', picks: 7, bets: 21, group: 2, basis: [2] },\n { id: 'superheinz', picks: 7, bets: 120, group: 8, basis: [2, 3, 4, 5, 6, 7] },\n { id: 'system7of8', picks: 8, bets: 8, group: 7, basis: [7] },\n { id: 'system6of8', picks: 8, bets: 28, group: 6, basis: [6] },\n { id: 'system5of8', picks: 8, bets: 56, group: 5, basis: [5] },\n { id: 'system4of8', picks: 8, bets: 70, group: 4, basis: [4] },\n { id: 'system3of8', picks: 8, bets: 56, group: 3, basis: [3] },\n { id: 'system2of8', picks: 8, bets: 28, group: 2, basis: [2] },\n { id: 'goliath', picks: 8, bets: 247, group: 8, basis: [2, 3, 4, 5, 6, 7, 8] },\n];\n","import { BetslipType } from '../../../../core/betslip-type';\nimport { IBetBuilderBetState } from '../../../bet-builder/models';\nimport { IComboBetState } from '../../../combo-bet/state';\nimport { isFreebetToken } from '../../../reward-tokens/services/reward-tokens.utils';\nimport { IRewardTokensState } from '../../../reward-tokens/state';\nimport { ISingleBetState } from '../../../single-bet/state';\nimport { SystemKey } from '../../../system-bet/models';\nimport { SYSTEMS } from '../../../system-bet/services/system-bet.constants';\nimport { ISystemBetState } from '../../../system-bet/state';\nimport { ITeaserBetState } from '../../../teaser-bet/state';\nimport { IBetslipTypeState } from '../../../types/state';\n\nexport interface TotalStake {\n stake: number | null;\n freeBetAmount: number | null;\n}\n\nfunction getStakeWithEachWay({ isEachWay, stake }: { isEachWay: boolean; stake: number | null }): number | null {\n if (!stake) {\n return null;\n }\n\n return isEachWay ? stake * 2 : stake;\n}\n\nexport function isFreeBetStake(stakes: TotalStake[]): boolean {\n const valueStakes = stakes.filter((stake) => !!stake.stake || !!stake.freeBetAmount);\n\n return valueStakes.length === 1 && !!valueStakes[0]?.freeBetAmount;\n}\n\nexport function getTotalStakesForCurrentType(typeState: IBetslipTypeState, tokenState: IRewardTokensState): TotalStake[] {\n const { currentSelectedType } = typeState.base;\n switch (currentSelectedType) {\n case BetslipType.Single:\n return getSingleBetStakes(typeState.singleBet, tokenState);\n case BetslipType.Combo:\n return getComboStake(typeState.comboBet, tokenState);\n case BetslipType.System:\n return getSystemStake(typeState.systemBet);\n default:\n return [];\n }\n}\n\nexport function getSingleBetStakes(singleState: ISingleBetState, tokensState: IRewardTokensState, filterLockedPicks: boolean = false): TotalStake[] {\n return Object.values(singleState.picks)\n .filter((p) => p.isSelected && (!p.isLocked || !filterLockedPicks))\n .map((p) => ({\n stake: getStakeWithEachWay(p),\n freeBetAmount: getFreeBetAmount(p.rewardTokenId, tokensState),\n }));\n}\n\nexport function getComboStake(comboState: IComboBetState, tokensState: IRewardTokensState): TotalStake[] {\n return [\n {\n stake: getStakeWithEachWay(comboState),\n freeBetAmount: getFreeBetAmount(comboState.rewardTokenId, tokensState),\n },\n ];\n}\n\nexport function getTeaserStake(teaserState: ITeaserBetState, tokensState: IRewardTokensState): TotalStake[] {\n return [\n {\n stake: teaserState.stake,\n freeBetAmount: getFreeBetAmount(teaserState.rewardTokenId, tokensState),\n },\n ];\n}\n\nexport function getBetBuilderStakes(betBuilderState: IBetBuilderBetState, tokensState: IRewardTokensState): TotalStake[] {\n return Object.values(betBuilderState.picks)\n .filter((p) => p.isSelected)\n .map((p) => ({\n stake: p.stake,\n freeBetAmount: getFreeBetAmount(p.rewardTokenId, tokensState),\n }));\n}\n\nexport function getFreeBetAmount(rewardTokenId: string | null, tokenState: IRewardTokensState) {\n const token = rewardTokenId ? tokenState.tokens[rewardTokenId] : null;\n const freeBetAmount = isFreebetToken(token) ? token.amount : null;\n\n return freeBetAmount;\n}\n\n/**\n * Sums multiple total stakes into a single total stake.\n * If both freebet and regular stake are set for a total stake, only the freebet value will be summed up.\n * This is done to ensure that adding freeBetAmount and stake leads to the correct total stake value that the bets would be placed with.\n * @param stakes The stakes to sum up.\n * @returns The total stake that the current bets will be placed with.\n */\nexport function combineTotalStakes(stakes: TotalStake[]): TotalStake {\n return stakes.reduce(\n (acc, curr) => {\n const freeBetAmount = curr.freeBetAmount === null ? acc.freeBetAmount : (acc.freeBetAmount ?? 0) + curr.freeBetAmount;\n const stake = curr.stake === null || curr.freeBetAmount ? acc.stake : (acc.stake ?? 0) + curr.stake;\n\n return {\n freeBetAmount,\n stake,\n };\n },\n {\n freeBetAmount: null,\n stake: null,\n },\n );\n}\n\nfunction getSystemStake(systemState: ISystemBetState): TotalStake[] {\n const typeKey = systemState.systemInfo?.key;\n\n if (!typeKey) {\n return [];\n }\n\n const stake = systemState.stake;\n\n const betCount =\n typeKey === SystemKey.BankerSingles\n ? Object.values(systemState.picks).filter((p) => p.isSelected && !p.isBanker).length\n : (SYSTEMS.find((s) => s.id === typeKey.toLowerCase())?.bets ?? null);\n\n return [\n {\n stake: betCount && stake ? stake * betCount : null,\n freeBetAmount: null,\n },\n ];\n}\n","import { BetslipType } from '../../../../core/betslip-type';\nimport { IBetBuilderBetState, IBetBuilderPickState } from '../../../bet-builder/models';\nimport { IComboBetState } from '../../../combo-bet/state';\nimport { isFreebetToken } from '../../../reward-tokens/services/reward-tokens.utils';\nimport { IRewardTokensState, TokensRecord } from '../../../reward-tokens/state';\nimport { ISingleBetPickState, ISingleBetState } from '../../../single-bet/state';\nimport { ISystemBetState } from '../../../system-bet/state';\nimport { ITeaserBetState } from '../../../teaser-bet/state';\nimport { IBetslipTypeState } from '../../../types/state';\nimport { TotalStake, getBetBuilderStakes, getComboStake, getSingleBetStakes, getTeaserStake } from './stake-utils';\n\nexport function getStakedTypes(typeState: IBetslipTypeState, tokens: TokensRecord): BetslipType[] {\n const linearTypes = typeState.base.linearTypes;\n\n const types: BetslipType[] = [];\n\n if (linearTypes[BetslipType.Single] && hasSingleBetStake(typeState.singleBet, tokens)) {\n types.push(BetslipType.Single);\n }\n\n if (linearTypes[BetslipType.BetBuilder] && hasBetBuilderBetStake(typeState.betBuilder, tokens)) {\n types.push(BetslipType.BetBuilder);\n }\n\n if (linearTypes[BetslipType.Combo] && hasComboBetStake(typeState.comboBet, tokens)) {\n types.push(BetslipType.Combo);\n }\n\n if (linearTypes[BetslipType.System] && hasSystemBetStake(typeState.systemBet)) {\n types.push(BetslipType.System);\n }\n\n if (linearTypes[BetslipType.Teaser] && hasTeaserBetStake(typeState.teaserBet, tokens)) {\n types.push(BetslipType.Teaser);\n }\n\n return types;\n}\n\nexport function hasSingleBetStake(singleState: ISingleBetState, tokens: TokensRecord): boolean {\n return Object.values(singleState.picks).some((a) => hasSingleBetPickStake(a, tokens));\n}\n\nexport function hasSingleBetPickStake(singleBetPickState: ISingleBetPickState, tokens: TokensRecord): boolean {\n return singleBetPickState.stake !== null || hasFreeBetStake(singleBetPickState.rewardTokenId, tokens);\n}\n\nexport function hasComboBetStake(comboState: IComboBetState, tokens: TokensRecord): boolean {\n return comboState.stake !== null || hasFreeBetStake(comboState.rewardTokenId, tokens);\n}\n\nexport function hasBetBuilderBetStake(betBuilderState: IBetBuilderBetState, tokens: TokensRecord): boolean {\n return Object.values(betBuilderState.picks).some((a) => hasBetBuilderBetPickStake(a, tokens));\n}\n\nexport function hasBetBuilderBetPickStake(betBuilderBetPickState: IBetBuilderPickState, tokens: TokensRecord): boolean {\n return betBuilderBetPickState.stake !== null || hasFreeBetStake(betBuilderBetPickState.rewardTokenId, tokens);\n}\n\nexport function hasTeaserBetStake(teaserState: ITeaserBetState, tokens: TokensRecord): boolean {\n return teaserState.stake != null || hasFreeBetStake(teaserState.rewardTokenId, tokens);\n}\n\nfunction hasFreeBetStake(tokenId: string | null, tokens: TokensRecord): boolean {\n return !!tokenId && isFreebetToken(tokens[tokenId]);\n}\n\nexport function hasSystemBetStake(systemState: ISystemBetState): boolean {\n return Object.values(systemState.linearTypeStakes).some((stake) => stake.stake !== null);\n}\n\nexport function getTotalStakesForAllTypes(typeState: IBetslipTypeState, tokensState: IRewardTokensState): TotalStake[] {\n const linearTypes = typeState.base.linearTypes;\n\n const singleBetStakes = linearTypes[BetslipType.Single] ? getSingleBetStakes(typeState.singleBet, tokensState, true) : [];\n const comboBetStakes = linearTypes[BetslipType.Combo] ? getComboStake(typeState.comboBet, tokensState) : [];\n const systemBetStakes = linearTypes[BetslipType.System] ? getSystemStakesLinear(typeState.systemBet) : [];\n const teaserBetStakes = linearTypes[BetslipType.Teaser] ? getTeaserStake(typeState.teaserBet, tokensState) : [];\n const betBuilderStakes = linearTypes[BetslipType.BetBuilder] ? getBetBuilderStakes(typeState.betBuilder, tokensState) : [];\n\n return [...singleBetStakes, ...comboBetStakes, ...systemBetStakes, ...teaserBetStakes, ...betBuilderStakes];\n}\n\nfunction getSystemStakesLinear(systemState: ISystemBetState) {\n return Object.values(systemState.linearTypeStakes).map((type) => {\n const stake = type.stake && type.stake * type.betCount;\n\n return {\n stake: type.isEachWay && stake ? stake * 2 : stake,\n freeBetAmount: null,\n };\n });\n}\n","import { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { isNil } from 'lodash-es';\n\nimport { BetslipType } from '../../../core/betslip-type';\nimport { BetslipPick } from '../../../core/picks/betslip-pick';\nimport { BetslipUnknownPick } from '../../../core/picks/betslip-unknown-pick';\nimport { PickId } from '../../../core/picks/pick-id';\nimport { PickOddsState } from '../../../core/picks/pick-models';\nimport { flattenPicks } from '../../../core/utils';\nimport {\n hasBetBuilderBetPickStake,\n hasComboBetStake,\n hasSingleBetPickStake,\n hasSystemBetStake,\n hasTeaserBetStake,\n} from '../../betplacement/services/stake/linear-stake-utils';\nimport { TokensRecord } from '../../reward-tokens/state';\nimport { IBetslipTypeState } from '../../types/state';\n\ninterface IPickTypeState {\n isLocked: boolean;\n isSelected: boolean;\n}\n\nexport function getSelectedPlaceablePicks(types: IBetslipTypeState, picksList: BetslipPick[], tokens: TokensRecord): BetslipPick[] {\n let picks: BetslipPick[] = [];\n\n const singleState = types.singleBet;\n const comboState = types.comboBet;\n const systemState = types.systemBet;\n const teaserState = types.teaserBet;\n const betBuilderState = types.betBuilder;\n\n const singlePicks = flattenPicks(picksList).filter((p) => {\n const typePick = singleState.picks[p.id.toString()];\n\n return typePick && hasSingleBetPickStake(typePick, tokens) && typePick.isSelected;\n });\n\n picks = [...singlePicks];\n\n const betBuilderPicks = picksList.filter((p) => {\n const typePick = betBuilderState.picks[p.id.toString()];\n\n return typePick && hasBetBuilderBetPickStake(typePick, tokens) && typePick.isSelected;\n });\n\n picks = [...picks, ...betBuilderPicks];\n\n if (hasComboBetStake(types.comboBet, tokens)) {\n const multiPicks = picksList.filter((p) => {\n const typePick = comboState.picks[p.id.toString()];\n\n return typePick.isSelected && picks.every((s) => !s.id.isEqual(p.id));\n });\n\n picks = [...picks, ...multiPicks];\n }\n\n if (hasSystemBetStake(types.systemBet)) {\n const systemPicks = flattenPicks(picksList).filter((p) => {\n const typePick = systemState.picks[p.id.toString()];\n\n return typePick && typePick.isSelected && picks.every((s) => !s.id.isEqual(p.id));\n });\n\n picks = [...picks, ...systemPicks];\n }\n\n if (hasTeaserBetStake(types.teaserBet, tokens)) {\n const teaserPicks = flattenPicks(picksList).filter((p) => {\n const typePick = teaserState.picks[p.id.toString()];\n\n return typePick?.isSelected && picks.every((s) => !s.id.isEqual(p.id));\n });\n\n picks = [...picks, ...teaserPicks];\n }\n\n return flattenPicks(picks);\n}\n\nexport function getSelectedLockedPicksForAllTypes(types: IBetslipTypeState, picksList: BetslipPick[], isLocked: boolean): BetslipPick[] {\n const flattenedPicksMap = flattenPicks(picksList).reduce<{ [pickId: string]: BetslipPick }>((acc, curr) => {\n acc[curr.id.toString()] = curr;\n\n return acc;\n }, {});\n\n const lockedPicks: { [pickId: string]: BetslipPick } = {};\n const updateLockedPicksForTypeState = (typeState: { [pickId: string]: IPickTypeState }): void => {\n Object.entries(typeState)\n .filter(([, typePick]) => typePick.isSelected && typePick.isLocked === isLocked)\n .forEach(([pickId]) => {\n lockedPicks[pickId] = flattenedPicksMap[pickId];\n });\n };\n\n updateLockedPicksForTypeState(types.singleBet.picks);\n updateLockedPicksForTypeState(types.comboBet.picks);\n updateLockedPicksForTypeState(types.systemBet.picks);\n updateLockedPicksForTypeState(types.betBuilder.picks);\n updateLockedPicksForTypeState(types.teaserBet.picks);\n\n return Object.values(lockedPicks).filter(isDefined);\n}\n\nexport function hasSelectedLockedPick(types: IBetslipTypeState, picksList: BetslipPick[], isLocked: boolean): boolean {\n return picksList.some((pick) => {\n const single = types.singleBet.picks[pick.id.toString()];\n const combo = types.comboBet.picks[pick.id.toString()];\n const system = types.systemBet.picks[pick.id.toString()];\n const teaser = types.teaserBet.picks[pick.id.toString()];\n\n if (!single || !combo || !system) {\n return false;\n }\n\n return (\n (single.isSelected && single.isLocked === isLocked) ||\n (combo.isSelected && combo.isLocked === isLocked) ||\n (system.isSelected && system.isLocked === isLocked) ||\n (teaser?.isSelected && teaser?.isLocked === isLocked)\n );\n });\n}\n\nexport function hasUnacceptedPick(types: IBetslipTypeState, picksList: BetslipPick[]): boolean {\n return picksList.some((pick) => {\n if (BetslipUnknownPick.isPick(pick)) {\n return false;\n }\n\n const single = types.singleBet.picks[pick.id.toString()];\n const combo = types.comboBet.picks[pick.id.toString()];\n const system = types.systemBet.picks[pick.id.toString()];\n\n if (!single || !combo || !system) {\n return false;\n }\n\n switch (pick.oddsState) {\n case PickOddsState.Closed:\n return true;\n\n case PickOddsState.Open:\n return single.isLocked || combo.isLocked || system.isLocked;\n\n case PickOddsState.Locked:\n return !single.isLocked || !combo.isLocked || !system.isLocked;\n }\n });\n}\n\nexport function isPickSelectedLocked(pickId: string, types: IBetslipTypeState, picksList: BetslipPick[], isLocked: boolean): boolean {\n const pick = picksList.find((p) => p.id.toString() === pickId);\n\n if (!pick) {\n return false;\n }\n\n const single = types.singleBet.picks[pickId];\n const combo = types.comboBet.picks[pickId];\n const system = types.systemBet.picks[pickId];\n const teaser = types.teaserBet.picks[pickId];\n\n return (\n (single.isSelected && single.isLocked === isLocked) ||\n (combo.isSelected && combo.isLocked === isLocked) ||\n (system.isSelected && system.isLocked === isLocked) ||\n (teaser?.isSelected && teaser?.isLocked === isLocked)\n );\n}\n\nexport function filterPicksForType(\n types: IBetslipTypeState,\n betslipType: BetslipType,\n picksList: BetslipPick[],\n filter: { isLocked?: boolean; isSelected?: boolean },\n): BetslipPick[] {\n let picks: { [id: string]: IPickTypeState } = {};\n switch (betslipType) {\n case BetslipType.Combo:\n picks = types.comboBet.picks;\n break;\n case BetslipType.System:\n picks = types.systemBet.picks;\n break;\n case BetslipType.Teaser:\n picks = types.teaserBet.picks;\n break;\n case BetslipType.BetBuilder:\n picks = types.betBuilder.picks;\n break;\n default:\n picks = types.singleBet.picks;\n break;\n }\n\n return filterTypePicks(picks, picksList, filter);\n}\n\nexport function filterTypePicks(\n typePicks: { [id: string]: IPickTypeState },\n picksList: BetslipPick[],\n filter: { isLocked?: boolean; isSelected?: boolean },\n): BetslipPick[] {\n return picksList.filter((pick) => {\n const pickState = typePicks[pick.id.toString()];\n if (!pickState) {\n return false;\n }\n\n const lockedFilterMatched = isNil(filter.isLocked) ? true : pickState.isLocked === filter.isLocked;\n const selectedFilterMatched = isNil(filter.isSelected) ? true : pickState.isSelected === filter.isSelected;\n\n return lockedFilterMatched && selectedFilterMatched;\n });\n}\n\nexport function isPickLocked(pickId: PickId, types: IBetslipTypeState): boolean {\n return (\n types.singleBet.picks[pickId.toString()]?.isLocked ||\n types.comboBet.picks[pickId.toString()]?.isLocked ||\n types.systemBet.picks[pickId.toString()]?.isLocked ||\n types.teaserBet.picks[pickId.toString()]?.isLocked\n );\n}\n","export enum ClientErrorType {\n NotLoggedInError = 'NotLoggedInError',\n UndoCashOutFailedError = 'UndoCashOutFailedError',\n RewardTokensMaxStakeError = 'RewardTokensMaxStakeError',\n RewardTokensMinLegsError = 'RewardTokensMinLegsError',\n RewardTokensTradingEntityMismatchError = 'RewardTokensTradingEntityMismatchError',\n RewardTokensForbiddenError = 'RewardTokensForbiddenError',\n RewardTokensGameTypeError = 'RewardTokensGameTypeError',\n RewardTokensSlipTypeError = 'RewardTokensSlipTypeError',\n RewardTokensMinStakeError = 'RewardTokensMinStakeError',\n RewardTokensTotalOddsError = 'RewardTokensTotalOddsError',\n RewardTokensSelectionLevelOddsError = 'RewardTokensSelectionLevelOddsError',\n RewardTokensBetBuilderError = 'RewardTokensBetBuilderError',\n BetBuilderMaxSelectionsPerPickError = 'BetBuilderMaxSelectionsPerPickError',\n RewardTokensMaxTotalOddsError = 'RewardTokensMaxTotalOddsError',\n RewardTokensSelectionMaxOddsError = 'RewardTokensSelectionMaxOddsError',\n}\n","import { IRewardToken } from '../../../../reward-tokens/reward-tokens.model';\nimport { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensBetBuilderError extends BetslipError {\n constructor(token: IRewardToken) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensBetBuilderError;\n this.hasClientValidation = true;\n this.token = token;\n }\n\n readonly token: IRewardToken;\n}\n","import { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensMinStakeError extends BetslipError {\n constructor(minimumStake: number) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMinStakeError;\n this.hasClientValidation = true;\n this.minimumStake = minimumStake;\n }\n\n readonly minimumStake: number;\n}\n","import { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensSelectionLevelOddsError extends BetslipError {\n constructor(selectionLevelMinimumOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensSelectionLevelOddsError;\n this.hasClientValidation = true;\n this.selectionLevelMinimumOdds = selectionLevelMinimumOdds;\n }\n\n readonly selectionLevelMinimumOdds: string;\n}\n","import { BetPlacementErrorIcon } from '../../bet-placement-error-icon';\nimport { BetslipError } from '../../betslip-error';\nimport { ClientErrorType } from '../../client-error-type';\n\nexport class RewardTokensTotalOddsError extends BetslipError {\n constructor(minimumOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensTotalOddsError;\n this.hasClientValidation = true;\n this.minimumOdds = minimumOdds;\n }\n\n readonly minimumOdds: string;\n}\n","import { PickId } from '../../../../core/picks/pick-id';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\nimport { ResultError } from '../result/result-error';\n\nexport abstract class GroupPickError extends BetslipError {\n protected constructor() {\n super();\n this.hasClientValidation = true;\n }\n}\n\nexport class GroupLinearPickError extends PreCheckErrorMixin(ResultError) {\n constructor(pickId: string) {\n super(pickId);\n }\n}\nexport class GroupMinSelectionsError extends GroupPickError {\n constructor() {\n super();\n }\n}\n\nexport class GroupMaxSelectionsError extends GroupPickError {\n constructor() {\n super();\n this.type = ClientErrorType.BetBuilderMaxSelectionsPerPickError;\n }\n}\n\nexport class GroupMaxSelectionsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupPickHasLiveLegsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupMinSelectionsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupPickInvalidPricePreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupWithUncombinableLegsError extends GroupPickError {\n constructor(\n public legIds: PickId[],\n public invalidPickIds: PickId[],\n ) {\n super();\n }\n}\n\nexport class GroupWithUncombinableLegsPreCheckError extends GroupLinearPickError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n\nexport class GroupWithClosedLegsError extends ResultError {\n constructor(\n public override pickId: string,\n public legIds: PickId[],\n ) {\n super(pickId);\n this.hasClientValidation = true;\n this.isHidden = true;\n }\n}\n\nexport class GroupWithSuspendedLegsError extends ResultError {\n constructor(\n public override pickId: string,\n public legIds: PickId[],\n ) {\n super(pickId);\n this.hasClientValidation = true;\n }\n}\n\nexport class GroupPickComboBetInvalidPrice extends GroupPickError {\n constructor(public invalidPickIds: PickId[]) {\n super();\n this.isHidden = true;\n }\n}\n\nexport class GroupPickHasLiveLegsError extends GroupPickError {\n constructor(public invalidPickIds: PickId[]) {\n super();\n }\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensForbiddenError extends PreCheckErrorMixin(BetslipError) {\n constructor() {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensForbiddenError;\n this.hasClientValidation = true;\n }\n}\n","import { GameType } from '@bpos/v1/sports-promo';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensGameTypeError extends PreCheckErrorMixin(BetslipError) {\n constructor(gameType: GameType) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensGameTypeError;\n this.hasClientValidation = true;\n this.gameType = gameType;\n }\n\n readonly gameType: GameType;\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensMaxStakeError extends PreCheckErrorMixin(BetslipError) {\n constructor(maximumStake: number) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMaxStakeError;\n this.hasClientValidation = true;\n this.maximumStake = maximumStake;\n }\n\n readonly maximumStake: number;\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\n\nexport class RewardTokensMaxTotalOddsError extends BetslipError {\n readonly maxOdds: string;\n\n constructor(maxOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMaxTotalOddsError;\n this.hasClientValidation = true;\n this.maxOdds = maxOdds;\n }\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensMinLegsError extends PreCheckErrorMixin(BetslipError) {\n constructor(minimumLegs: number) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensMinLegsError;\n this.hasClientValidation = true;\n this.minimumLegs = minimumLegs;\n }\n\n readonly minimumLegs: number;\n}\n","import { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\n\nexport class RewardTokensSelectionLevelMaxOddsError extends BetslipError {\n readonly selectionLevelMaxOdds: string;\n\n constructor(maxOdds: string) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensSelectionMaxOddsError;\n this.hasClientValidation = true;\n this.selectionLevelMaxOdds = maxOdds;\n }\n}\n","import { BetSlipType } from '@bpos';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { ClientErrorType } from '../client-error-type';\nimport { PreCheckErrorMixin } from '../pre-check/pre-check-error';\n\nexport class RewardTokensSlipTypeError extends PreCheckErrorMixin(BetslipError) {\n constructor(slipType: BetSlipType) {\n super();\n this.icon = BetPlacementErrorIcon.Error;\n this.type = ClientErrorType.RewardTokensSlipTypeError;\n this.hasClientValidation = true;\n this.slipType = slipType;\n }\n\n readonly slipType: BetSlipType;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementError } from '../bet-placement-error';\nimport { CleanStrategy } from '../betslip-error';\n\nexport class TechnicalError extends BetPlacementError {\n constructor() {\n super();\n this.cleanStrategy = CleanStrategy.Manually;\n this.type = PlacementErrorType.TechnicalError;\n }\n\n reason: string;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementError } from '../bet-placement-error';\n\nexport class IncorrectBetCount extends BetPlacementError {\n constructor(expected?: number, received?: number) {\n super();\n this.expectedBetCount = typeof expected === 'undefined' ? null : expected;\n this.receivedBetCount = typeof received === 'undefined' ? null : received;\n this.hasClientValidation = true;\n this.type = PlacementErrorType.IncorrectOptionCount;\n }\n\n readonly expectedBetCount: number | null;\n readonly receivedBetCount: number | null;\n}\n","import { IncorrectBetCount } from '../general/incorrect-bet-count';\nimport { ErrorDetailsSpecifier, PreCheckErrorMixin } from './pre-check-error';\n\nexport class IncorrectOptionsCountPreCheckError extends PreCheckErrorMixin(IncorrectBetCount) {\n constructor(\n expected: number,\n received: number,\n public errorDetails: ErrorDetailsSpecifier | null,\n ) {\n super(expected, received);\n }\n\n override readonly expectedBetCount: number;\n override readonly receivedBetCount: number;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipError } from '../betslip-error';\nimport { PreCheckErrorMixin } from './pre-check-error';\n\nexport class NoBetbuilderSystemPreCheckError extends PreCheckErrorMixin(BetslipError) {\n constructor() {\n super();\n this.type = PlacementErrorType.NoBetbuilderSystemAllowed;\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { StakeError } from '../stake-error';\n\nexport class NotEnoughMoney extends StakeError {\n constructor(\n readonly userMoney: number,\n public stake?: number,\n ) {\n super();\n this.icon = BetPlacementErrorIcon.Warning;\n this.priority = 1000; // Not Enough Money last error\n this.type = PlacementErrorType.InsufficientMoney;\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { UserError } from './user-error';\n\nexport class OverallMaxWinPerUser extends UserError {\n constructor(comboWin: number | undefined, maxComboWin: number | undefined) {\n super();\n this.comboWin = comboWin == null ? null : comboWin;\n this.maxComboWin = maxComboWin == null ? null : maxComboWin;\n this.icon = BetPlacementErrorIcon.Warning;\n this.type = PlacementErrorType.OverallMaxWinPerUserExceeded;\n }\n\n comboWin: number | null;\n maxComboWin: number | null;\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { SlipId } from '../../../bet-generation/models';\nimport { BetPlacementError } from '../../errors/bet-placement-error';\nimport { StakeHintInfo } from '../../models/stake-validation.models';\nimport { BetslipTypePickErrors } from '../../state';\n\nconst ignoreNewStakeHintType = [PlacementErrorType.UnderMinimumOddsBoostReward, PlacementErrorType.UnderMinimumRiskFreeCompensation];\n\nexport function getStakeLimitErrorInfo(errors: { pickErrors: BetslipTypePickErrors; slipErrors: Record }): {\n betslipStakeHintInfo: { newStakeHint: number; type: PlacementErrorType } | null;\n pickStakeHintInfos: { newStakeHint: number; pickId: string; type: PlacementErrorType }[];\n} {\n const betslipStakeLimitExceededError = Object.values(errors.slipErrors)\n .flatMap((e) => e)\n .find((betslipError) => shouldUpdateStake(betslipError));\n\n const pickStakeLimitExceededErrors = Object.values(errors.pickErrors)\n .flatMap((e) => Object.values(e))\n .flatMap((resultErrors) => resultErrors)\n .filter((e) => shouldUpdateStake(e));\n\n if (!betslipStakeLimitExceededError && pickStakeLimitExceededErrors.length === 0) {\n return { betslipStakeHintInfo: null, pickStakeHintInfos: [] };\n }\n\n if (betslipStakeLimitExceededError) {\n return {\n betslipStakeHintInfo: { newStakeHint: betslipStakeLimitExceededError.newStakeHint!, type: betslipStakeLimitExceededError.type },\n pickStakeHintInfos: [],\n };\n }\n\n return {\n betslipStakeHintInfo: null,\n pickStakeHintInfos: pickStakeLimitExceededErrors.map((pickError) => ({\n type: pickError.type,\n pickId: pickError.pickId,\n newStakeHint: pickError.newStakeHint!,\n })),\n };\n}\n\nexport function getStakeHints(errors: { pickErrors: BetslipTypePickErrors; slipErrors: Record }): StakeHintInfo {\n const slipStakeHintInfos = Object.entries(errors.slipErrors).flatMap(([key, slipErrors]) =>\n slipErrors.filter(shouldUpdateStake).map((error) => ({ newStakeHint: error.newStakeHint!, slipId: key, type: error.type })),\n );\n\n // This is only relevant for multi-singles\n const pickStakeHintInfos = Object.entries(errors.pickErrors[BetslipType.Single])\n .flatMap(([pickId, e]) => e.filter((error) => shouldUpdateStake(error)).map((error) => ({ pickId, error })))\n .map(({ pickId, error }) => ({\n type: error.type,\n // we cannot use error.pickId here\n // after bet placement we are also mapping normal betslip errors to ResultError and therefore error.pickId could be undefined\n pickId,\n newStakeHint: error.newStakeHint!,\n }));\n\n return {\n slipStakeHintInfos,\n pickStakeHintInfos,\n };\n}\n\nexport function shouldUpdateStake(error: BetPlacementError): boolean {\n return isNewStakeHintError(error) && !ignoreNewStakeHintType.includes(error.type);\n}\n\nexport function isNewStakeHintError(error: BetPlacementError): boolean {\n return typeof error.newStakeHint === 'number' && error.newStakeHint > 0;\n}\n","import { CashoutErrorType } from '@bpos/v1/cashout';\nimport { isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { PlacementErrorType } from '@frontend/sports/types/betslip';\nimport { orderBy } from 'lodash-es';\n\nimport { BetslipType } from '../../../../core/betslip-type';\nimport { BaseBetslipGroupPick } from '../../../../core/picks/betslip-group-pick';\nimport { BetslipPick } from '../../../../core/picks/betslip-pick';\nimport { GroupEnabledPickIds } from '../../../../core/picks/pick-id';\nimport { PickOddsState } from '../../../../core/picks/pick-models';\nimport { StakeUpdateErrorModel } from '../../../betplacement/models';\nimport { hasUnacceptedPick } from '../../../picks/services/linear-betslip-pick.utils';\nimport { IBetslipTypeState } from '../../../types/state';\nimport { BetPlacementError } from '../../errors/bet-placement-error';\nimport { BetslipError, ErrorOrigin } from '../../errors/betslip-error';\nimport { ClientErrorType } from '../../errors/client-error-type';\nimport { RewardTokensBetBuilderError } from '../../errors/general/betandget/reward-tokens-bet-builder-error';\nimport { RewardTokensMinStakeError } from '../../errors/general/betandget/reward-tokens-min-stake-error';\nimport { RewardTokensSelectionLevelOddsError } from '../../errors/general/betandget/reward-tokens-selection-level-odds-error';\nimport { RewardTokensTotalOddsError } from '../../errors/general/betandget/reward-tokens-total-odds-error';\nimport {\n GroupLinearPickError,\n GroupPickComboBetInvalidPrice,\n GroupPickError,\n GroupPickHasLiveLegsError,\n GroupPickInvalidPricePreCheckError,\n GroupWithClosedLegsError,\n GroupWithSuspendedLegsError,\n GroupWithUncombinableLegsError,\n} from '../../errors/general/group-pick-error';\nimport { RewardTokensForbiddenError } from '../../errors/general/reward-tokens-forbidden-error';\nimport { RewardTokensGameTypeError } from '../../errors/general/reward-tokens-game-type-error';\nimport { RewardTokensMaxStakeError } from '../../errors/general/reward-tokens-max-stake-error';\nimport { RewardTokensMaxTotalOddsError } from '../../errors/general/reward-tokens-max-total-odds-error';\nimport { RewardTokensMinLegsError } from '../../errors/general/reward-tokens-min-legs-error';\nimport { RewardTokensSelectionLevelMaxOddsError } from '../../errors/general/reward-tokens-selection-level-max-odds-error';\nimport { RewardTokensSlipTypeError } from '../../errors/general/reward-tokens-slip-type-error';\nimport { TechnicalError } from '../../errors/general/technical-error';\nimport { NotifyUserError } from '../../errors/notify-user-error';\nimport { IncorrectOptionsCountPreCheckError } from '../../errors/pre-check/incorrect-options-count-pre-check-error';\nimport { NoBetbuilderSystemPreCheckError } from '../../errors/pre-check/no-betbuilder-system-pre-check-error';\nimport { OddsChangedPreCheckError } from '../../errors/pre-check/odds-changed-pre-check-error';\nimport { OverMaximumStakePreCheckError } from '../../errors/pre-check/over-maximum-stake-pre-check-error';\nimport { StakeNotDividablePreCheckError } from '../../errors/pre-check/stake-not-dividable-pre-check-error';\nimport { UnderMinimumStakePreCheckError } from '../../errors/pre-check/under-minimum-stake-pre-check-error';\nimport { UnderMinimumWinningsPreCheckError } from '../../errors/pre-check/under-minimum-winnings-pre-check-error';\nimport { ResultError } from '../../errors/result/result-error';\nimport { NotEnoughMoney } from '../../errors/user/not-enough-money';\nimport { OverallMaxWinPerUser } from '../../errors/user/overall-max-win-per-user';\nimport { BetslipTypePickErrors, IBetslipErrorsState, IBetslipPickErrors, SlipErrors } from '../../state';\nimport { getSlipErrorsForType } from './betslip-errors-state-utils';\nimport { isNewStakeHintError } from './stake-limit-errors-info-utils';\n\nexport const enum OverallMaxWinPerUserHintType {\n Any = 'Any',\n WithHint = 'WithHint',\n WithoutHint = 'WithoutHint',\n}\n\nexport const isPickStatusLocked = function (error: BetslipError): boolean {\n return (\n error.type === PlacementErrorType.OptionInvisible ||\n error.type === CashoutErrorType.ChangedOptionStatus ||\n error.type === PlacementErrorType.CutOffDateReached ||\n error.type === PlacementErrorType.InvalidOption ||\n error.type === PlacementErrorType.MissingInfo ||\n error.type === PlacementErrorType.NotPlayable ||\n error.type === PlacementErrorType.OpenDateNotReached ||\n error.type === PlacementErrorType.OptionError\n );\n};\n\nexport const isPickStatusClosed = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.CutOffDateReached || error.type === PlacementErrorType.OpenDateNotReached;\n};\n\nexport const isPickStatusOddsChanged = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.OddsChanged;\n};\n\n/**\n * Is the given error changes pick status and the user should accept changes.\n *\n * @param error\n */\nexport const isPickStatusChangeError = function (error: BetslipError): boolean {\n return isPickStatusLocked(error) || isPickStatusClosed(error) || isPickStatusOddsChanged(error) || isGroupPickOfferChange(error);\n};\n\nexport const isTeaserBetPickStatusChangeError = function (error: BetslipError): boolean {\n return isPickStatusLocked(error) || isPickStatusClosed(error);\n};\n\nexport const isImportantError = function (error: BetslipError): boolean {\n return !(isPickStatusChangeError(error) || isErrorComboPrevention(error));\n};\n\nexport const isBetslipStatusInsufficientMoney = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.InsufficientMoney;\n};\n\nexport const isNotifyUserChangeError = function (error: BetslipError): error is NotifyUserError {\n return 'userHasBeenNotified' in error;\n};\n\nexport const isPlaceBlockingError = function (error: BetslipError): boolean {\n return (\n isErrorComboPrevention(error) ||\n error.type === PlacementErrorType.BlockedAsInsider ||\n error.type === PlacementErrorType.IncorrectOptionCount ||\n error.type === PlacementErrorType.IncorrectBankerCount ||\n error.type === PlacementErrorType.UnderMinimumCompulsoryOdds ||\n isNotDividableStakeErrorPreCheck(error)\n );\n};\n\nexport const isUnderMinimumStakeErrorPreCheck = (error: BetslipError): error is UnderMinimumStakePreCheckError =>\n error.type === PlacementErrorType.StakeBelowMinimumLimit;\n\nexport const isUnderMinimumWinningsErrorPreCheck = (error: BetslipError): error is UnderMinimumWinningsPreCheckError =>\n error.type === PlacementErrorType.UnderMinimumWinnings;\n\nexport const isOverMaximumStakeError = (error: BetslipError): error is OverMaximumStakePreCheckError =>\n error.type === PlacementErrorType.OverMaxBetLimit;\n\nexport const isNotDividableStakeErrorPreCheck = (error: BetslipError): error is StakeNotDividablePreCheckError =>\n error.type === PlacementErrorType.NotWholeStake;\n\nexport const isPickStatusOddsChangedPreCheck = function (error: BetslipError): error is OddsChangedPreCheckError {\n return error.type === PlacementErrorType.OddsChanged;\n};\n\nexport const isNoBetBuilderSystemPreCheck = function (error: BetslipError): error is NoBetbuilderSystemPreCheckError {\n return error instanceof NoBetbuilderSystemPreCheckError;\n};\n\n/**\n * This function help us to check when we need to show yellow indication that has not compatible picks.\n */\nexport const isErrorComboPrevention = function (error: BetslipError): boolean {\n return error.type === PlacementErrorType.ComboPrevention || error.type === PlacementErrorType.MinimumCombo;\n};\n\nexport const isStakeError = function (error: BetslipError): boolean {\n return (\n error.type === PlacementErrorType.StakeBelowMinimumLimit ||\n error.type === PlacementErrorType.UnderMinimumWinnings ||\n error.type === PlacementErrorType.OverMaxBetLimit ||\n error.type === PlacementErrorType.NotWholeStake ||\n error.type === PlacementErrorType.InsufficientMoney\n );\n};\n\nexport const shouldThisPlacementErrorLockEditBet = (error: BetslipError): boolean => {\n return error.type === PlacementErrorType.TechnicalError;\n};\n\nexport const isIncorrectOptionsCountError = (error: BetslipError): error is IncorrectOptionsCountPreCheckError =>\n error instanceof IncorrectOptionsCountPreCheckError;\n\nexport const isRewardTokensSlipTypeError = (error: BetslipError): error is RewardTokensSlipTypeError => error instanceof RewardTokensSlipTypeError;\n\nexport const isRewardTokensMaxStakeError = (error: BetslipError): error is RewardTokensMaxStakeError => error instanceof RewardTokensMaxStakeError;\n\nexport const isRewardTokensError = (error: BetslipError): boolean =>\n [\n RewardTokensSlipTypeError,\n RewardTokensMaxStakeError,\n RewardTokensMinStakeError,\n RewardTokensMinLegsError,\n RewardTokensSelectionLevelOddsError,\n RewardTokensGameTypeError,\n RewardTokensForbiddenError,\n RewardTokensTotalOddsError,\n RewardTokensBetBuilderError,\n RewardTokensMaxTotalOddsError,\n RewardTokensSelectionLevelMaxOddsError,\n ].some((t) => error instanceof t);\n\nexport const isOverallMaxWinPerUserExceededError = (error: BetslipError, hint: OverallMaxWinPerUserHintType): error is OverallMaxWinPerUser => {\n if (error instanceof OverallMaxWinPerUser) {\n switch (hint) {\n case OverallMaxWinPerUserHintType.Any:\n return true;\n case OverallMaxWinPerUserHintType.WithHint:\n return !!error.newStakeHint && error.newStakeHint > 0;\n case OverallMaxWinPerUserHintType.WithoutHint:\n return error.newStakeHint === 0;\n }\n }\n\n return false;\n};\n\nexport const hasCriticalBetslipAndTypeErrorForType = (errorState: IBetslipErrorsState, type: BetslipType): boolean =>\n errorState.betslipErrors.some(isCriticalError) || errorState.betslipTypeErrors[type].some(isCriticalError);\n\nexport const hasCriticalSlipErrorsForType = (\n errorState: IBetslipErrorsState,\n type: BetslipType,\n types: IBetslipTypeState,\n isLinear: boolean,\n): boolean => getSlipErrorsForType(type, errorState.slipErrors, types, isLinear).some(isCriticalError);\n\nexport const hasCriticalErrorForSlip = (errorState: IBetslipErrorsState, slipId: string): boolean =>\n (errorState.slipErrors[slipId] ?? []).some(isCriticalError);\n\nexport const isCriticalError = (error: BetslipError): boolean => {\n return (\n !isStakeError(error) &&\n !(error.type === PlacementErrorType.NoMultiSinglesAllowed) &&\n !(error instanceof BetPlacementError && isNewStakeHintError(error)) &&\n !(error instanceof TechnicalError)\n );\n};\n\nexport const hasInsufficientMoneyError = (errors: BetslipError[]): boolean => {\n return errors.some(isBetslipStatusInsufficientMoney);\n};\n\nexport const getStakeUpdateErrors = (slipErrors: SlipErrors): StakeUpdateErrorModel[] => {\n return Object.entries(slipErrors).flatMap(([key, errors]) =>\n errors.filter((e) => isUnderMinimumStakeErrorPreCheck(e)).map((e) => ({ error: e as UnderMinimumStakePreCheckError, slipId: key })),\n );\n};\n\n/**\n * Push a new error in the errors collection\n *\n * @param currentErrors\n * @param newError\n */\nexport const errorCollectionPusher = (currentErrors: TError[], newError: TError): TError[] => {\n if (currentErrors.some((e) => newError.equals(e))) {\n // Error already exists.\n return currentErrors;\n }\n\n return currentErrors.filter((e) => e.constructor !== newError.constructor).concat(newError);\n};\n\nexport function getPlacementError(errors: BetslipError[]): BetslipError | null {\n const filteredErrors = filterBetPlacementErrors(errors);\n\n return filteredErrors[0] || null;\n}\n\nexport function getPlacementErrorFromAllErrors(\n betslipErrors: BetslipError[],\n typePickErrors: BetslipTypePickErrors,\n slipErrors: SlipErrors,\n): BetslipError | null {\n const allPickErrors = Object.values(typePickErrors).flatMap((pickErrors) => Object.values(pickErrors).flatMap((errors) => errors));\n\n const allSlipErrors = Object.values(slipErrors).flatMap((errors) => errors);\n\n return getPlacementError([...betslipErrors, ...allPickErrors, ...allSlipErrors]);\n}\n\nfunction filterBetPlacementErrors(errors: BetslipError[]): BetslipError[] {\n const filteredErrors = errors.filter((error) => !error.isHidden && error.origin === ErrorOrigin.BetPlacement);\n\n return orderBy(filteredErrors, (x) => x.priority);\n}\n\nconst exitFreebetErrors: (PlacementErrorType | CashoutErrorType | ClientErrorType)[] = [\n PlacementErrorType.InvalidFreeBet,\n PlacementErrorType.FreeBetExpired,\n PlacementErrorType.FreeBetNotActive,\n];\n\nexport function isExitFreeBetError(error: BetslipError | null): boolean {\n return !!error && exitFreebetErrors.includes(error.type);\n}\n\nexport function isFinishedError(error: BetslipError): boolean {\n return error.code === 'COS' || error.code === 'CODR';\n}\n\nexport function canShowPickErrorAsBetslipError(betslipErrors: BetslipError[], currentSelectedBetslipType: BetslipType | null): boolean {\n // For certain NewStakeHintErrors we receive them per pick from BPOS but in specific situations we need to show them for the entire betslip\n return currentSelectedBetslipType !== BetslipType.Single && betslipErrors.every((error) => isStakeError(error));\n}\n\nexport function isNewStakeHintPlacementError(error: BetslipError): boolean {\n return error instanceof BetPlacementError && error.origin === ErrorOrigin.BetPlacement && typeof error.newStakeHint === 'number';\n}\n\nexport const isNotEnoughMoney = (error: BetslipError): error is NotEnoughMoney => error instanceof NotEnoughMoney;\n\nexport function checkOfferChanged(pickErrors: ResultError[]): boolean {\n return pickErrors.some((e) => isPickStatusChangeError(e));\n}\n\nexport function checkOfferChangedLinear(picks: BetslipPick[], typeState: IBetslipTypeState, pickErrors: ResultError[]): boolean {\n const oddsChanged = pickErrors.some((e) => isPickStatusOddsChanged(e));\n\n return oddsChanged || hasUnacceptedPick(typeState, picks);\n}\n\nexport function isGroupLockedPickError(error: ResultError): boolean {\n return error instanceof GroupWithSuspendedLegsError;\n}\n\nexport function isGroupOfferChangedError(error: BetslipError): boolean {\n return error instanceof ResultError && isGroupLockedPickError(error);\n}\n\nexport function isBetPlacementBlockingError(error: BetslipError): boolean {\n return (\n ![\n PlacementErrorType.TechnicalError,\n PlacementErrorType.OverAskOfferError,\n PlacementErrorType.InsufficientMoney,\n PlacementErrorType.DoubleBetPrevention,\n ].includes(error.type as PlacementErrorType) && !isNewStakeHintError(error as BetPlacementError)\n );\n}\n\nexport function pickErrorsHasBlockingErrors(pickErrors: IBetslipPickErrors): boolean {\n return Object.values(pickErrors).some((errors) => errors.some((err) => err instanceof GroupWithSuspendedLegsError));\n}\n\nexport function getClosedLegIdsWithinValidGroups(picks: BetslipPick[], picksErrors: IBetslipPickErrors) {\n return (\n picks\n .filter(BaseBetslipGroupPick.isPick)\n //ignore closed picks within a group if the group is suspended\n .filter((g) => g.oddsState === PickOddsState.Open)\n .map((g) => picksErrors[g.id.toString()]?.find((error) => error instanceof GroupWithClosedLegsError) as GroupWithClosedLegsError)\n .filter(isDefined)\n .flatMap((g) => g.legIds as GroupEnabledPickIds[])\n );\n}\n\nexport function getSuspendedLegIdsWithinValidGroups(picks: BetslipPick[], picksErrors: IBetslipPickErrors) {\n return (\n picks\n .filter(BaseBetslipGroupPick.isPick)\n //ignore suspended picks within a group if the group is suspended\n .filter((g) => g.oddsState === PickOddsState.Open)\n .map((g) => picksErrors[g.id.toString()]?.find((error) => error instanceof GroupWithSuspendedLegsError) as GroupWithSuspendedLegsError)\n .filter(isDefined)\n .flatMap((g) => g.legIds as GroupEnabledPickIds[])\n );\n}\n\nexport function isGroupPickOfferChange(error: BetslipError): error is GroupWithSuspendedLegsError | GroupWithClosedLegsError {\n return error instanceof GroupWithSuspendedLegsError || error instanceof GroupWithClosedLegsError;\n}\n\n/**\n * Checks if a pick should be displayed on the pick level in the betslip\n * @param error The error to check\n * @returns True if the error should be displayed on the pick level\n */\nexport function isDisplayablePickError(error: ResultError): boolean {\n return (\n !error.isHidden &&\n !(\n error.type === PlacementErrorType.ComboPrevention ||\n error.type === PlacementErrorType.CutOffDateReached ||\n error.type === PlacementErrorType.OptionInvisible ||\n error.type === PlacementErrorType.OddsChanged\n )\n );\n}\n\nexport function isGroupPickError(error: BetslipError): error is GroupPickError {\n return error instanceof GroupPickError || error instanceof GroupLinearPickError;\n}\n\nexport function isGroupPickInvalidPriceError(error: BetslipError): error is GroupPickComboBetInvalidPrice {\n return error instanceof GroupPickComboBetInvalidPrice || error instanceof GroupPickInvalidPricePreCheckError;\n}\n\nexport function isGroupWithUncombinableLegsError(error: BetslipError): error is GroupWithUncombinableLegsError {\n return error instanceof GroupWithUncombinableLegsError;\n}\n\nexport function isDrawerGroupError(\n error: BetslipError,\n): error is GroupWithUncombinableLegsError | GroupPickComboBetInvalidPrice | GroupPickHasLiveLegsError {\n return isGroupWithUncombinableLegsError(error) || isGroupPickInvalidPriceError(error) || isGroupPickHasLiveLegsError(error);\n}\n\nexport function isGroupPickHasLiveLegsError(error: BetslipError): error is GroupPickHasLiveLegsError {\n return error instanceof GroupPickHasLiveLegsError;\n}\n\nexport function groupPickHasWarning(error: BetslipError): error is GroupPickHasLiveLegsError | GroupPickComboBetInvalidPrice {\n return isGroupPickInvalidPriceError(error) || isGroupPickHasLiveLegsError(error);\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\nimport { createSelector } from '@ngrx/store';\n\nimport { betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { SlipId } from '../bet-generation/models';\nimport { getBetBuilderSlipId, getSingleSlipId } from '../bet-generation/services/slip.utils';\nimport { LinearBetBuilderPickId } from '../linear-bet-builder/models';\nimport { selectBetBuilderPickStakeFactory } from '../linear-bet-builder/selectors';\nimport { betslipPicksListSelector, selectBetslipFlattenedPicksList } from '../picks/selectors';\nimport { selectSingleBetAllSameStake, selectSingleBetPickStakeFactory, singleBetPicksGeneralStakeSelector } from '../single-bet/selectors';\nimport { selectLinearStakeForSlip } from '../stake/selectors';\nimport { selectIsSystemBetExpanded } from '../system-bet/selectors';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { betslipTypeStateSelector, editBetAddedPicksListSelector, editBetPicksListSelector, selectIsSingleBetSelected } from '../types/selectors';\nimport { BetslipError, ErrorOrigin } from './errors/betslip-error';\nimport { MinSelectionsBetbuilderError } from './errors/general/min-selections-betbuilder-error';\nimport { getSlipErrorsForType } from './services/utils/betslip-errors-state-utils';\nimport {\n canShowPickErrorAsBetslipError,\n checkOfferChanged,\n checkOfferChangedLinear,\n isBetslipStatusInsufficientMoney,\n isDisplayablePickError,\n isDrawerGroupError,\n isErrorComboPrevention,\n isGroupPickError,\n isGroupPickOfferChange,\n isNotEnoughMoney,\n isPickStatusChangeError,\n isRewardTokensError,\n isStakeError,\n} from './services/utils/betslip-errors-utils';\nimport { BetslipTypePickErrors, SlipErrors } from './state';\n\n// State\n\n/** Selects the entire error state */\nexport const selectErrorsState = createSelector(betslipSelector, (state) => state.errors);\n\n/** Selects the state for errors that effect the entire betslip */\nexport const selectBetslipErrors = createSelector(selectErrorsState, (state) => state.betslipErrors);\n\n/** Selects the state for errors that affect individual picks*/\nexport const selectPickErrorsState = createSelector(selectErrorsState, (errorsState) => errorsState.pickErrors);\n\n/** Selects the state for errors that affect individual slips (multi-singles, combo, system) */\nexport const selectSlipErrorsState = createSelector(selectErrorsState, (errorState) => errorState.slipErrors);\n\n/** Selects the state for errors that affect individual betslip types (single, combo, system, teaser) */\nexport const selectTypeErrorsState = createSelector(selectErrorsState, (errorState) => errorState.betslipTypeErrors);\n\n// Slip Errors\n\nexport const selectSlipErrorsFactory = (slipKey: SlipId) =>\n createSelector(selectSlipErrorsState, (slipErrors: SlipErrors): BetslipError[] | undefined => slipErrors[slipKey]);\n\nexport const selectSlipErrorsForTypeFactory = (activeType: BetslipType) =>\n createSelector(selectSlipErrorsState, betslipTypeStateSelector, selectIsLinearBetslip, (state, types, isLinear) => {\n return getSlipErrorsForType(activeType, state, types, isLinear);\n });\n\nexport const selectCurrentSlipErrors = createSelector(\n selectSlipErrorsState,\n betslipTypeStateSelector,\n selectIsLinearBetslip,\n (state, types, isLinear) => {\n const currentType = types.base.currentSelectedType;\n\n return !currentType ? [] : getSlipErrorsForType(currentType, state, types, isLinear);\n },\n);\n\n/**\n * Selects all slip errors for a multi single pick with the given PickId\n */\nexport const selectSingleSlipErrorsFactory = (pickId: PickId) =>\n createSelector(selectSlipErrorsState, (errorState) => errorState[getSingleSlipId(pickId)] ?? []);\n\nexport const selectBetBuilderSlipErrorsFactory = (pickId: PickId) =>\n createSelector(selectSlipErrorsState, (errorState) => errorState[getBetBuilderSlipId(pickId)] ?? []);\n\nexport const selectHasAnySlipErrors = createSelector(selectSlipErrorsState, (state) => Object.values(state).some((p) => p.length > 0));\n\n// Betslip Type Errors\n\nexport const selectBetslipTypeErrorsFactory = (type: BetslipType) => createSelector(selectTypeErrorsState, (state) => state[type] ?? []);\n\nexport const selectCurrentBetslipTypeErrors = createSelector(selectTypeErrorsState, betslipCurrentTypeSelector, (state, type) =>\n type ? state[type] : [],\n);\n\nexport const selectHasAnyTypeErrors = createSelector(selectTypeErrorsState, (state) => Object.values(state).some((p) => p.length > 0));\n\n// Betslip Errors\n\nexport const selectAllBetslipErrorsForType = (type: BetslipType) =>\n createSelector(\n selectBetslipErrors,\n selectBetslipTypeErrorsFactory(type),\n selectSlipErrorsForTypeFactory(type),\n (betslipErrors, typeErrors, slipErrors) => {\n return [...betslipErrors, ...typeErrors, ...slipErrors];\n },\n );\n\nexport const selectAllCurrentBetslipErrors = createSelector(\n selectBetslipErrors,\n selectCurrentBetslipTypeErrors,\n selectCurrentSlipErrors,\n (betslipErrors, typeErrors, slipErrors) => {\n return [...betslipErrors, ...typeErrors, ...slipErrors];\n },\n);\n\nexport const selectAllBetBuilderErrors = createSelector(\n selectBetslipErrors,\n selectSlipErrorsForTypeFactory(BetslipType.BetBuilder),\n selectBetslipTypeErrorsFactory(BetslipType.BetBuilder),\n (betslipErrors, typeErrors, slipErrors) => {\n return [...betslipErrors, ...typeErrors, ...slipErrors];\n },\n);\n\nexport const selectHasAnyBetslipErrors = createSelector(selectBetslipErrors, (errors) => errors.length > 0);\n\n// Pick Errors\n\nexport const selectPickErrorsForTypeFactory = (type: BetslipType) =>\n createSelector(selectErrorsState, (state) => {\n return state.pickErrors[type] ?? {};\n });\n\nexport const selectAllPickErrorsForTypeFactory = (type: BetslipType) =>\n createSelector(selectPickErrorsForTypeFactory(type), (pickErrors) => {\n return Object.values(pickErrors).flatMap((errors) => errors) ?? [];\n });\n\nexport const selectAllPickErrors = createSelector(selectPickErrorsState, (pickErrors) =>\n Object.values(pickErrors).flatMap((errors) => Object.values(errors).flatMap((e) => e)),\n);\n\nexport const selectTypePickErrors = createSelector(selectErrorsState, (state) => {\n return state.pickErrors;\n});\n\n/**\n * Selects all errors for a pick with the given PickId for the currently selected betslip type (tabbed betslip)\n */\nexport const selectCurrentPickErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectPickErrorsState, betslipCurrentTypeSelector, (pickErrors, type) => {\n if (!type) {\n return [];\n }\n\n return pickErrors[type][pickId.toString()] ?? [];\n });\n\n/** Selects all errors for a pick with the given PickId for the given betslip type */\nexport const selectTypePickErrorsForPickFactory = (pickId: PickId, type: BetslipType) =>\n createSelector(selectPickErrorsState, (pickErrors) => {\n return pickErrors[type][pickId.toString()] ?? [];\n });\n\nexport const selectAllPickErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectPickErrorsState, (pickErrors) => {\n const pickIdString = pickId.toString();\n\n return Object.values(pickErrors).flatMap((errors) => errors[pickIdString] ?? []);\n });\n\nexport const selectCurrentPicksErrors = createSelector(selectPickErrorsState, betslipCurrentTypeSelector, (pickErrors, type) =>\n type ? pickErrors[type] : {},\n);\n\nexport const selectAllCurrentPickErrors = createSelector(selectCurrentPicksErrors, (currentPicks) =>\n Object.values(currentPicks).flatMap((picks) => picks),\n);\n\nexport const selectCurrentPicksErrorsForAllPicks = createSelector(selectCurrentPicksErrors, (picksErrors) =>\n Object.values(picksErrors).flatMap((p) => p),\n);\n\nexport const selectHasAnyPickErrors = createSelector(selectPickErrorsState, (pickErrors) =>\n Object.values(pickErrors).some((p) => Object.values(p).some((e) => e.length > 0)),\n);\n\nexport const selectPicksWithCurrentErrors = createSelector(betslipPicksListSelector, selectCurrentPicksErrors, (pickList, pickErrors) =>\n pickList.map((pick) => ({\n pick,\n errors: pickErrors[pick.id.toString()] || [],\n })),\n);\n\nexport const selectPicksWithAllErrors = createSelector(selectBetslipFlattenedPicksList, selectPickErrorsState, (pickList, pickErrors) =>\n pickList.map((pick) => ({\n pick,\n errors: Object.values(pickErrors).flatMap((errors) => errors[pick.id.toString()] ?? []),\n })),\n);\n\nexport const selectEditBetPicksWithErrors = createSelector(\n editBetPicksListSelector,\n editBetAddedPicksListSelector,\n selectPickErrorsForTypeFactory(BetslipType.EditBet),\n (pickList, pickAddedList, pickErrors) =>\n [...pickList, ...pickAddedList].map((pick) => ({\n pick,\n errors: pickErrors[pick.id.toString()] || [],\n })),\n);\n\n// Specific selectors\nexport const selectAllSingleBetBetslipErrors = createSelector(selectAllBetslipErrorsForType(BetslipType.Single), (errors) => errors);\n\nexport const selectAllCurrentErrors = createSelector(selectAllCurrentBetslipErrors, selectAllCurrentPickErrors, (betslipErrors, pickErrors) => [\n ...betslipErrors,\n ...pickErrors,\n]);\n\nexport const selectNotEnoughMoneyError = createSelector(selectBetslipErrors, (errors) => errors.find(isNotEnoughMoney));\n\nexport const selectCurrentVisibleBetslipErrorsByPriority = createSelector(selectAllCurrentBetslipErrors, (errors) =>\n errors.filter((e) => !e.isHidden).sort((a, b) => b.priority - a.priority),\n);\n\nexport const selectHasCurrentComboPreventionError = createSelector(selectCurrentPicksErrorsForAllPicks, (errors) =>\n errors.some(isErrorComboPrevention),\n);\n\nexport const selectHasComboPreventionForTypeFactory = (type: BetslipType) =>\n createSelector(selectAllPickErrorsForTypeFactory(type), (errors) => errors.some(isErrorComboPrevention));\n\nexport const selectHasInsufficientMoneyError = createSelector(selectBetslipErrors, (errors) => errors.some(isBetslipStatusInsufficientMoney));\n\nexport const selectHasNonPickErrors = createSelector(\n selectHasAnyBetslipErrors,\n selectHasAnyTypeErrors,\n selectHasAnySlipErrors,\n (hasBetslipErrors, hasTypeErrors, hasSlipErrors) => hasBetslipErrors || hasTypeErrors || hasSlipErrors,\n);\n\nexport const selectHasAnyErrors = createSelector(\n selectHasNonPickErrors,\n selectHasAnyPickErrors,\n (hasNonPickErrors, hasPickErrors) => hasNonPickErrors || hasPickErrors,\n);\n\nexport const selectHasMinSelectionsBetBuilderError = createSelector(selectBetslipErrors, (errors) =>\n errors.some((error) => error instanceof MinSelectionsBetbuilderError),\n);\n\nexport const selectCurrentStakeError = createSelector(\n selectAllCurrentBetslipErrors,\n selectIsSingleBetSelected,\n selectSingleBetAllSameStake,\n (errors, isSingleBet, singleBetAllSameStake) =>\n isSingleBet && !singleBetAllSameStake\n ? errors.find((betslipError) => betslipError.type === PlacementErrorType.InsufficientMoney)\n : errors.find(isStakeError),\n);\n\nexport const selectCanShowPickErrorAsBetslipError = createSelector(selectAllCurrentBetslipErrors, betslipCurrentTypeSelector, (betslipErrors, type) =>\n canShowPickErrorAsBetslipError(betslipErrors, type),\n);\n\nexport const selectHasOfferChanged = createSelector(\n selectAllPickErrors,\n betslipPicksListSelector,\n selectIsLinearBetslip,\n betslipTypeStateSelector,\n (pickErrors, picks, isLinear, types) => (isLinear ? checkOfferChangedLinear(picks, types, pickErrors) : checkOfferChanged(pickErrors)),\n);\n\nexport const selectHasOfferChangedFromBetPlacement = createSelector(selectAllPickErrors, (errors) =>\n errors.some((e) => isPickStatusChangeError(e) && e.origin === ErrorOrigin.BetPlacement),\n);\n\nexport const selectDisplayablePickErrorsFactory = (pickId: PickId, betslipType: BetslipType) =>\n createSelector(selectTypePickErrorsForPickFactory(pickId, betslipType), (errors) => errors.filter((e) => isDisplayablePickError(e)));\n\nexport const selectTabbedPickErrorsFactory = (pickId: PickId, betslipType: BetslipType) =>\n createSelector(selectDisplayablePickErrorsFactory(pickId, betslipType), selectCanShowPickErrorAsBetslipError, (errors, hidePickErrors) =>\n !hidePickErrors ? errors : [],\n );\n\nexport const selectCurrentSummarySlipErrors = createSelector(\n selectSlipErrorsState,\n betslipTypeStateSelector,\n selectIsLinearBetslip,\n (state, types, isLinear) => {\n const currentType = types.base.currentSelectedType;\n\n return !currentType || currentType === BetslipType.Single ? [] : getSlipErrorsForType(currentType, state, types, isLinear);\n },\n);\n\nexport const selectAllCurrentSummaryErrors = createSelector(\n selectBetslipErrors,\n selectCurrentBetslipTypeErrors,\n selectCurrentSummarySlipErrors,\n selectAllCurrentPickErrors,\n selectCanShowPickErrorAsBetslipError,\n (betslipErrors, typeErrors, slipErrors, pickErrors, showPickErrors) => {\n return !showPickErrors ? [...betslipErrors, ...typeErrors, ...slipErrors] : [...betslipErrors, ...typeErrors, ...slipErrors, ...pickErrors];\n },\n);\n\nexport const selectSingleBetPickErrorsFactory = (pickId: PickId) =>\n createSelector(\n selectTypePickErrorsForPickFactory(pickId, BetslipType.Single),\n selectSingleSlipErrorsFactory(pickId),\n (pickErrors, slipErrors) => [...pickErrors, ...slipErrors],\n );\n\nexport const selectSinglePickErrorStateFactory = (pickId: PickId) =>\n createSelector(selectSingleBetPickErrorsFactory(pickId), selectSingleBetPickStakeFactory(pickId), (errors, stake) => ({\n errors,\n stake,\n }));\n\nexport const selectBetBuilderBetPickErrorsFactory = (pickId: PickId) =>\n createSelector(\n selectDisplayablePickErrorsFactory(pickId, BetslipType.BetBuilder),\n selectBetBuilderSlipErrorsFactory(pickId),\n (pickErrors, slipErrors) => [...pickErrors, ...slipErrors],\n );\n\nexport const selectBetBuilderPickErrorStateFactory = (pickId: LinearBetBuilderPickId) =>\n createSelector(selectBetBuilderBetPickErrorsFactory(pickId), selectBetBuilderPickStakeFactory(pickId), (errors, stake) => ({\n errors,\n stake,\n }));\n\nexport const selectSinglePickStakeErrorStateFactory = (pickId: PickId) =>\n createSelector(\n selectSinglePickErrorStateFactory(pickId),\n singleBetPicksGeneralStakeSelector,\n selectHasInsufficientMoneyError,\n (state, generalStake, hasInsufficientFunds) => ({\n stakeError: state.errors.find(isStakeError),\n stake: state.stake,\n showInsufficientFundsError: hasInsufficientFunds && !!state.stake?.actualStake && !generalStake?.actualStake,\n }),\n );\n\nexport const selectLinearSlipErrorStateFactory = (slipId: SlipId, betslipType: BetslipType) =>\n createSelector(\n selectSlipErrorsFactory(slipId),\n selectLinearStakeForSlip(slipId, betslipType),\n // We should not have any slip errors displayed when we have a combo prevention error\n selectHasComboPreventionForTypeFactory(betslipType),\n (errors, stake, hasComboPrevention) => ({ errors: hasComboPrevention ? [] : errors, stake }),\n );\n\nconst selectDrawerGroupErrorFactory = (pickId: PickId) =>\n createSelector(selectGroupErrors, (typeErrors) => {\n return typeErrors\n .filter(isDrawerGroupError)\n .find((invalidPriceError) => invalidPriceError.invalidPickIds.some((invalidid) => invalidid.isEqual(pickId)));\n });\n\nconst selectGroupErrors = createSelector(selectCurrentBetslipTypeErrors, (typeErrors) => {\n return typeErrors.filter(isGroupPickError);\n});\n\nconst selectGroupOfferChangeErrorsFactory = (pickId: PickId) =>\n createSelector(selectAllPickErrorsForPickFactory(pickId), (pickErrors) => {\n return pickErrors.filter(isGroupPickOfferChange);\n });\n\nexport const selectRelevantGroupErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectDrawerGroupErrorFactory(pickId), selectGroupOfferChangeErrorsFactory(pickId), (groupPickError, groupOfferChange) => ({\n groupPickError,\n groupOfferChange,\n }));\n\nexport const selectAllDisplayablePickErrorsForTypeFactory = (type: BetslipType) =>\n createSelector(selectAllPickErrorsForTypeFactory(type), (pickErrors) => {\n return pickErrors.filter(isDisplayablePickError);\n });\n\nexport const selectTypeErrors = (type: BetslipType) =>\n createSelector(\n selectBetslipTypeErrorsFactory(type),\n selectSlipErrorsForTypeFactory(type),\n selectAllDisplayablePickErrorsForTypeFactory(type),\n (typeErrors, slipErrors, pickErrors) => {\n return [...typeErrors, ...slipErrors, ...pickErrors];\n },\n );\n\nexport const selectSystemContainerErrors = createSelector(\n selectBetslipTypeErrorsFactory(BetslipType.System),\n selectAllDisplayablePickErrorsForTypeFactory(BetslipType.System),\n (typeErrors, pickErrors) => {\n return [...typeErrors, ...pickErrors];\n },\n);\n\nexport const selectContainerErrors = (type: BetslipType) =>\n createSelector(selectIsLinearBetslip, selectSystemContainerErrors, selectTypeErrors(type), (isLinear, systemContainerErrors, typeErrors) => {\n return isLinear && type === BetslipType.System ? systemContainerErrors : typeErrors;\n });\n\nexport const selectComboContainerModuleErrors = createSelector(selectTypeErrors(BetslipType.Combo), (errors) => {\n return errors;\n});\nexport const selectCurrentSlipTokenErrors = createSelector(selectCurrentSlipErrors, (slipErrors) => slipErrors.filter(isRewardTokensError));\nexport const selectBetBuilderDrawerErrors = createSelector(selectCurrentBetslipTypeErrors, selectCurrentSlipTokenErrors, (errors, slipErrors) => [\n ...errors,\n ...slipErrors,\n]);\n\nexport const selectLinearGlobalIndiactorErrorsForPickFactory = (pickId: PickId) =>\n createSelector(selectPickErrorsState, selectIsSystemBetExpanded, (pickErrors, isSystemBetExpanded) => {\n const pickIdString = pickId.toString();\n\n const globalIndicatorErrors: BetslipTypePickErrors = {\n ...pickErrors,\n SYSTEM: isSystemBetExpanded ? pickErrors.SYSTEM : {},\n };\n\n return Object.values(globalIndicatorErrors).flatMap((errors) => errors[pickIdString] ?? []);\n });\n","import { BetslipType } from '../../../core/betslip-type';\nimport { PickId, pickIdFactory } from '../../../core/picks/pick-id';\nimport { IBetBuilderPickState } from '../../bet-builder/models';\nimport { IComboBetState } from '../../combo-bet/state';\nimport { ISingleBetPickState } from '../../single-bet/state';\nimport { ITeaserBetState } from '../../teaser-bet/state';\nimport { IBetslipTypeState } from '../../types/state';\nimport { AccaBoostToken, IRewardToken, RewardTokenContext, SelectedRewardTokenState } from '../reward-tokens.model';\nimport { IRewardTokenEligibility, IRewardTokenEligibilityState, TokensRecord } from '../state';\nimport { byAccaPriority, isAccaBoostToken } from './reward-tokens.utils';\n\nexport type RewardTokenAwareState = IComboBetState | ISingleBetPickState | ITeaserBetState | IBetBuilderPickState;\nexport type TokenAwarePicksState = Record;\n\nexport function getSelectedToken(tokens: TokensRecord, type: RewardTokenAwareState): IRewardToken | null {\n if (!type?.rewardTokenId) {\n return null;\n }\n\n return tokens[type.rewardTokenId] ?? null;\n}\n\nexport function getSelectedTokenForContext(context: RewardTokenContext, tokens: TokensRecord, types: IBetslipTypeState): IRewardToken | null {\n switch (context.betslipType) {\n case BetslipType.Single:\n return getSelectedTokenForPick(context.pickId, tokens, types.singleBet.picks);\n case BetslipType.Combo:\n return getSelectedToken(tokens, types.comboBet);\n case BetslipType.Teaser:\n return getSelectedToken(tokens, types.teaserBet);\n case BetslipType.BetBuilder:\n return getSelectedTokenForPick(context.pickId, tokens, types.betBuilder.picks);\n default:\n return null;\n }\n}\n\nexport function getSelectedTokenAndEligibilityForContext(\n context: RewardTokenContext,\n eligibilityState: IRewardTokenEligibilityState,\n tokens: TokensRecord,\n types: IBetslipTypeState,\n): [IRewardToken | null, IRewardTokenEligibility | null] {\n switch (context.betslipType) {\n case BetslipType.Single: {\n return getSelectedTokenAndEligibilityForPick(context.pickId, tokens, types.singleBet.picks, eligibilityState.singleBet);\n }\n case BetslipType.Combo: {\n return getSelectedTokenAndEligibility(tokens, types.comboBet, eligibilityState.comboBet);\n }\n case BetslipType.Teaser: {\n return getSelectedTokenAndEligibility(tokens, types.teaserBet, eligibilityState.teaserBet);\n }\n case BetslipType.BetBuilder: {\n return getSelectedTokenAndEligibilityForPick(context.pickId, tokens, types.betBuilder.picks, eligibilityState.betBuilder);\n }\n default:\n return [null, null];\n }\n}\n\nexport function getAllAssignedTokens(tokens: TokensRecord, types: IBetslipTypeState): IRewardToken[] {\n return getAllAssignedTokensWithFilter(tokens, types, false);\n}\n\nexport function getAllSelectedTokens(tokens: TokensRecord, types: IBetslipTypeState): IRewardToken[] {\n return getAllAssignedTokensWithFilter(tokens, types, true);\n}\n\nexport function getTokenSelectionContext(tokenId: string, types: IBetslipTypeState): RewardTokenContext | undefined {\n if (types.comboBet.rewardTokenId === tokenId) {\n return {\n betslipType: BetslipType.Combo,\n };\n }\n\n if (types.teaserBet.rewardTokenId === tokenId) {\n return {\n betslipType: BetslipType.Teaser,\n };\n }\n\n const selectedSinglePickId = getPickIdBySelectedToken(tokenId, types.singleBet.picks);\n\n if (selectedSinglePickId) {\n return {\n pickId: selectedSinglePickId,\n betslipType: BetslipType.Single,\n };\n }\n\n const selectedBetBuilderPickId = getPickIdBySelectedToken(tokenId, types.betBuilder.picks);\n\n if (selectedBetBuilderPickId) {\n return {\n pickId: selectedBetBuilderPickId,\n betslipType: BetslipType.BetBuilder,\n };\n }\n\n return undefined;\n}\n\nexport function getSelectedTokenState(\n rewardTokenId: string,\n tokens: Record,\n eligibilityState: IRewardTokenEligibility[],\n): SelectedRewardTokenState {\n const token = tokens[rewardTokenId] ?? null;\n const tokenEligibility = (eligibilityState ?? []).find((e) => rewardTokenId && e.tokenId === rewardTokenId) ?? null;\n\n return {\n tokenId: rewardTokenId,\n token,\n tokenEligibility,\n };\n}\n\nexport function getEligibilityStateForContext(\n context: RewardTokenContext,\n eligibilityState: IRewardTokenEligibilityState,\n): IRewardTokenEligibility[] {\n switch (context.betslipType) {\n case BetslipType.Single: {\n if (!context.pickId) {\n return [];\n }\n\n return eligibilityState.singleBet[context.pickId.toString()] ?? [];\n }\n case BetslipType.Combo:\n return eligibilityState.comboBet;\n case BetslipType.Teaser:\n return eligibilityState.teaserBet;\n case BetslipType.BetBuilder: {\n if (!context.pickId) {\n return [];\n }\n\n return eligibilityState.betBuilder[context.pickId.toString()] ?? [];\n }\n default:\n return [];\n }\n}\n\nexport function getSelectedAndEligibleAccaBoost(\n eligibilityState: IRewardTokenEligibilityState,\n tokens: TokensRecord,\n types: IBetslipTypeState,\n): AccaBoostToken | null {\n // Acca Boost token can only be applied to a combo bet\n const selectedToken = getSelectedTokenForContext({ betslipType: BetslipType.Combo }, tokens, types);\n\n if (!isAccaBoostToken(selectedToken)) {\n return null;\n }\n\n const isEligible = eligibilityState.comboBet.find((e) => e.tokenId === selectedToken.userTokenId)?.isEligible;\n\n return isEligible ? selectedToken : null;\n}\n\nexport function getEligibleAccaBoost(eligibilityState: IRewardTokenEligibilityState, tokens: TokensRecord): AccaBoostToken | null {\n const allEligibleTokens = eligibilityState.comboBet.filter((e) => e.isEligible).map((e) => tokens[e.tokenId]);\n\n return allEligibleTokens.filter(isAccaBoostToken).sort(byAccaPriority)[0] ?? null;\n}\n\nexport function isTokenRecoverable(tokenId: string, tokens: Record, eligibilityState: IRewardTokenEligibility[]): boolean {\n const token = tokens[tokenId];\n\n if (!token) {\n return false;\n }\n\n const eligibility = eligibilityState.find((e) => e.tokenId === tokenId);\n\n return !!eligibility && eligibility.isEligible;\n}\n\n// Helper functions to avoid code duplication\n\nfunction getSelectedTokenForPick(pickId: PickId | undefined, tokens: TokensRecord, awareStates: TokenAwarePicksState): IRewardToken | null {\n if (!pickId) {\n return null;\n }\n const pickState = awareStates[pickId.toString()];\n\n return getSelectedToken(tokens, pickState);\n}\n\nfunction getSelectedTokenAndEligibilityForPick(\n pickId: PickId | undefined,\n tokens: TokensRecord,\n awareStates: TokenAwarePicksState,\n eligibilityStates: Record,\n): [IRewardToken | null, IRewardTokenEligibility | null] {\n if (!pickId) {\n return [null, null];\n }\n\n const pickState = awareStates[pickId.toString()];\n const eligibilities = eligibilityStates[pickId.toString()] ?? [];\n\n return getSelectedTokenAndEligibility(tokens, pickState, eligibilities);\n}\n\nfunction getSelectedTokenAndEligibility(\n tokens: TokensRecord,\n awareState: RewardTokenAwareState,\n eligibilities: IRewardTokenEligibility[],\n): [IRewardToken | null, IRewardTokenEligibility | null] {\n const token = getSelectedToken(tokens, awareState);\n const tokenEligibility = token ? (eligibilities.find((e) => e.tokenId === token.userTokenId) ?? null) : null;\n\n return [token, tokenEligibility];\n}\n\nfunction getAllAssignedTokensWithFilter(tokens: TokensRecord, types: IBetslipTypeState, filterUnselectedPicks: boolean = false): IRewardToken[] {\n const singlePicks = Object.values(types.singleBet.picks);\n const betBuilderPicks = Object.values(types.betBuilder.picks);\n\n const picks = [...singlePicks, ...betBuilderPicks];\n\n const filteredPicks = filterUnselectedPicks ? picks.filter((pick) => pick.isSelected) : picks;\n\n const pickTokenIds = filteredPicks.map((pick) => pick.rewardTokenId);\n\n const tokenIds = [...pickTokenIds, types.comboBet.rewardTokenId, types.teaserBet.rewardTokenId].filter((id) => !!id) as string[];\n\n return tokenIds.map((id) => tokens[id]).filter((token) => !!token);\n}\n\nfunction getPickIdBySelectedToken(selectedTokenId: string, awarePicksState: TokenAwarePicksState): PickId | undefined {\n const pick = Object.entries(awarePicksState).find(([, value]) => value.rewardTokenId === selectedTokenId);\n\n return pick ? pickIdFactory(pick[0]) : undefined;\n}\n","import { Nullable, Optional, isDefined } from '@frontend/sports/common/core/utils/extended-types';\nimport { BestOddsFixtureLikeInput } from 'packages/sports/web/app/src/best-odds-guarantee/models/models';\nimport { IRewardTokenData, RewardTokenType } from 'packages/sports/web/app/src/tokens-base/token-base.models';\n\nimport { ParticipantPickType } from '../../../core/picks/pick-models';\nimport {\n BetslipV2HorseRaceOptionMarketPick,\n BetslipV2HorseRaceWinParticipantPick,\n BetslipV2HorseRaceXCastPick,\n} from '../../../core/picks/sport-specific/betslip-v2-horse-race-picks';\nimport { IBetslipTypeState } from '../../types/state';\nimport {\n BestOddsGuaranteedToken,\n BetStationFreeBetToken,\n EdsToken,\n EdsTokenWithEligiblePicks,\n IRewardToken,\n RewardTokenContext,\n} from '../reward-tokens.model';\nimport { IBetstationTokenState, IBetstationTokensState, IEdsTokenState, IRewardTokensState, TokensRecord } from '../state';\nimport { getSelectedTokenForContext } from './linear-reward-tokens.utils';\nimport { convertToBestOddsFixtureLikeInput } from './reward-tokens.utils';\nimport { TokenValidationInput } from './validators/criteria-validator-base.service';\n\nexport function isBetStationFreebetToken(token: IRewardToken | IRewardTokenData | null): token is BetStationFreeBetToken {\n return !!token && token.rewardTokenType === RewardTokenType.BetStationFreebet;\n}\n\nexport function isBestOddsGuaranteedToken(token: IRewardToken | IRewardTokenData | null): token is BestOddsGuaranteedToken {\n return !!token && token.rewardTokenType === RewardTokenType.BestOddsGuaranteed;\n}\n\nexport function isEdsToken(token: Optional): token is EdsToken {\n return !!token && token.rewardTokenType === RewardTokenType.Eds;\n}\n\nexport function getBestOddsGuaranteedTokensContext(state: IRewardTokensState): { valid: boolean; token: BestOddsGuaranteedToken | undefined } {\n //It is guaranteed in SPC service that only one BOG token will be returned\n const token = Object.values(state.tokens).find(isBestOddsGuaranteedToken);\n\n const eligibility = state.betstationTokens.bogTokenState.eligibility;\n\n return {\n valid: !!eligibility?.isEligible,\n token,\n };\n}\n\nexport function toBestOddsLikeFixtures(context: TokenValidationInput): Nullable[] {\n const picks = context.allPicks;\n\n return picks.map((pick) => {\n if (\n (BetslipV2HorseRaceOptionMarketPick.isPick(pick) &&\n pick.pickType === ParticipantPickType.Win &&\n (pick.isStartingPriceAvailable() || pick.isFixedPriceAvailable())) ||\n BetslipV2HorseRaceWinParticipantPick.isPick(pick) ||\n BetslipV2HorseRaceXCastPick.isPick(pick)\n ) {\n const fixture = convertToBestOddsFixtureLikeInput(pick);\n\n return { ...fixture, pickId: pick.id.toString() };\n } else {\n return null;\n }\n });\n}\n\nexport function getSelectedBogToken(bogTokenState: IBetstationTokenState, tokens: Record): BestOddsGuaranteedToken | null {\n const token = bogTokenState.selectedTokenId ? tokens[bogTokenState.selectedTokenId] : null;\n\n return isBestOddsGuaranteedToken(token) ? token : null;\n}\n\nexport function getSelectedBetStationFreeBetToken(\n extendedTokens: IBetstationTokensState,\n tokens: Record,\n): BetStationFreeBetToken | null {\n const tokenId = extendedTokens.betStationFreeBet.selectedTokenId;\n\n if (!tokenId) {\n return null;\n }\n\n const token = tokens[tokenId];\n\n return isBetStationFreebetToken(token) ? token : null;\n}\n\nexport function getSelectedBetStationTokenForContext(\n context: RewardTokenContext,\n types: IBetslipTypeState,\n betstationTokensState: IBetstationTokensState,\n tokens: TokensRecord,\n): IRewardToken | null {\n const tokenId = betstationTokensState.betStationFreeBet.selectedTokenId;\n\n if (tokenId) {\n const token = tokens[tokenId];\n\n return isBetStationFreebetToken(token) ? token : null;\n }\n\n return getSelectedTokenForContext(context, tokens, types);\n}\n\nexport function getSelectedEdsTokensWithEligiblePicksIds(state: IEdsTokenState, tokens: Record): EdsTokenWithEligiblePicks[] {\n return state.selectedTokenIds\n ?.map((id) => {\n const token = tokens[id];\n\n return isEdsToken(token)\n ? {\n token,\n eligiblePicksIds: state.eligibilities?.find((e) => e.tokenId === id)?.eligiblePicksIds || [],\n }\n : undefined;\n })\n .filter(isDefined);\n}\n","import { RewardTargetType } from '@bpos/v1/sports-promo/tokens';\nimport { createSelector } from '@ngrx/store';\n\nimport { betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { BetslipV2OptionMarketPick } from '../../core/picks/betslip-v2-option-market-pick';\nimport { PickId, pickIdFactory } from '../../core/picks/pick-id';\nimport { betslipPicksListSelector } from '../picks/selectors';\nimport {\n selectSingleBetSinglePickId,\n singleBetPickSelectorFactory,\n singleBetPicksSelector,\n singleBetSelectedPicksSelector,\n} from '../single-bet/selectors';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { betslipTypeStateSelector } from '../types/selectors';\nimport { selectHasComboPreventionForTypeFactory, selectHasMinSelectionsBetBuilderError } from '../validation/selectors';\nimport { RewardTokenContext } from './reward-tokens.model';\nimport {\n getBestOddsGuaranteedTokensContext,\n getSelectedBetStationFreeBetToken,\n getSelectedBogToken,\n} from './services/betstation-reward-tokens.utils';\nimport {\n getAllAssignedTokens,\n getAllSelectedTokens,\n getEligibilityStateForContext,\n getSelectedTokenAndEligibilityForContext,\n getSelectedTokenForContext,\n getTokenSelectionContext,\n} from './services/linear-reward-tokens.utils';\nimport {\n getAcquisitionRewardToken,\n getAllEligibleTokens,\n getNonAccaTokens,\n isAcquisitionEligible,\n isFreebetToken,\n isOddsBoostToken,\n} from './services/reward-tokens.utils';\n\nexport const rewardTokensStateSelector = createSelector(betslipSelector, (state) => state.rewardTokens);\nexport const rewardTokensOnBoardingSelector = createSelector(rewardTokensStateSelector, (state) => state.onboardingState);\n\nexport const selectRewardTokens = createSelector(rewardTokensStateSelector, (state) => state.tokens);\nexport const selectRewardTokensList = createSelector(selectRewardTokens, (tokens) => Object.values(tokens));\n\nexport const selectTokensEligibilityState = createSelector(rewardTokensStateSelector, (state) => state.eligibilityState);\n\nexport const selectHasNewCustomerOfferPick = createSelector(betslipPicksListSelector, (pickList) =>\n pickList.some((pick) => pick.isNewCustomerOfferPick),\n);\n\nexport const selectTokensStateContext = createSelector(\n selectTokensEligibilityState,\n selectRewardTokens,\n betslipTypeStateSelector,\n (eligibilityState, tokens, types) => ({\n eligibilityState,\n tokens,\n types,\n }),\n);\n\nexport const selectAllEligibleTokens = createSelector(selectTokensEligibilityState, selectRewardTokens, (eligibilityState, tokens) =>\n getAllEligibleTokens(eligibilityState, tokens),\n);\n\nexport const selectNonAccaTokens = createSelector(selectRewardTokensList, (tokens) => {\n return getNonAccaTokens(tokens);\n});\n\nexport const selectTokenForContextFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokensStateContext, ({ tokens, types }) => getSelectedTokenForContext(context, tokens, types));\n};\n\nexport const selectTokenAndEligibilityFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokensStateContext, ({ tokens, eligibilityState, types }) =>\n getSelectedTokenAndEligibilityForContext(context, eligibilityState, tokens, types),\n );\n};\n\nexport const selectEligibleTokenFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokenAndEligibilityFactory(context), ([token, eligibility]) => (eligibility?.isEligible ? token : null));\n};\n\nexport const selectFreeBetTokenFactory = (context: RewardTokenContext) => {\n return createSelector(selectEligibleTokenFactory(context), (token) => (isFreebetToken(token) ? token : null));\n};\n\nexport const selectOddsBoostTokenFactory = (context: RewardTokenContext) => {\n return createSelector(selectEligibleTokenFactory(context), (token) => (isOddsBoostToken(token) ? token : null));\n};\n\nexport const selectAllSelectedTokens = createSelector(selectTokensStateContext, ({ tokens, types }) => getAllSelectedTokens(tokens, types));\n\nexport const selectAllAssignedTokens = createSelector(selectTokensStateContext, ({ tokens, types }) => getAllAssignedTokens(tokens, types));\n\nexport const selectEligibilityStateFactory = (context: RewardTokenContext) => {\n return createSelector(selectTokensEligibilityState, (eligibilityState) => getEligibilityStateForContext(context, eligibilityState));\n};\n\nexport const selectAllTokensAndEligibilityStateFactory = (context: RewardTokenContext) => {\n return createSelector(selectRewardTokens, selectEligibilityStateFactory(context), (tokens, eligibilityState) => ({\n tokens,\n eligibilityState,\n }));\n};\n\nexport const selectSelectedTokenForContext = (context: RewardTokenContext) => {\n return createSelector(selectRewardTokens, betslipTypeStateSelector, (tokens, types) => {\n return getSelectedTokenForContext(context, tokens, types);\n });\n};\n\nexport const selectHasVisibleTokensForContext = (context: RewardTokenContext) => {\n return createSelector(\n selectAllSelectedTokens,\n selectTokenForContextFactory(context),\n selectEligibilityStateFactory(context),\n (allSelectedTokens, selectedToken, eligibilityState) => {\n return eligibilityState.some((eligibility) => {\n const currentSelected = selectedToken?.userTokenId === eligibility.tokenId;\n\n // this makes sure that already selected tokens in a different slip don't get shown\n return currentSelected || (eligibility.isEligible && allSelectedTokens.every((token) => token.userTokenId !== eligibility.tokenId));\n });\n },\n );\n};\n\nexport const selectUnselectedSingleBetPickForAcquisitionToken = createSelector(\n singleBetPicksSelector,\n selectRewardTokensList,\n betslipCurrentTypeSelector,\n selectIsLinearBetslip,\n (singleBetPicks, tokens, currentSelectedType, isLinear) => {\n const acquisitionRewardToken = getAcquisitionRewardToken(tokens);\n\n if (!acquisitionRewardToken || (!isLinear && currentSelectedType !== BetslipType.Single)) {\n return;\n }\n\n const unselectedPicks = Object.entries(singleBetPicks)\n .filter(([, pick]) => !pick.isSelected && pick.rewardTokenId === acquisitionRewardToken.userTokenId)\n .map(([k]) => k);\n\n return unselectedPicks.length > 0 ? { pickId: unselectedPicks[0] } : undefined;\n },\n);\n\nexport const selectSingleBetPickForAcquisitionToken = createSelector(\n singleBetSelectedPicksSelector,\n selectRewardTokensList,\n selectTokensEligibilityState,\n betslipCurrentTypeSelector,\n selectIsLinearBetslip,\n selectAllSelectedTokens,\n selectHasMinSelectionsBetBuilderError,\n (selectedPicks, tokens, eligibilityState, currentSelectedType, isLinear, allSelectedTokens, hasMinSelectionsBetBuilderError) => {\n const acquisitionRewardToken = getAcquisitionRewardToken(tokens);\n const isAcquisitionTokenSelected = allSelectedTokens.some((x) => x.rewardTargetType === RewardTargetType.WelcomeOffer);\n\n if (\n !acquisitionRewardToken ||\n (!isLinear && currentSelectedType !== BetslipType.Single) ||\n isAcquisitionTokenSelected ||\n hasMinSelectionsBetBuilderError\n ) {\n return;\n }\n\n const acquisitionEligiblePicks = Object.entries(eligibilityState.singleBet)\n .filter(\n ([pickId, eligibilityTokens]) =>\n selectedPicks[pickId] &&\n !selectedPicks[pickId].rewardTokenId &&\n eligibilityTokens.some((x) => isAcquisitionEligible(x, acquisitionRewardToken.userTokenId)),\n )\n .map(([k]) => k);\n\n return acquisitionEligiblePicks.length > 0 ? { pickId: acquisitionEligiblePicks[0], tokenId: acquisitionRewardToken.userTokenId } : undefined;\n },\n);\n\nexport const selectSingleBetStandardRewardTokensContext = createSelector(singleBetPicksSelector, (singleBetPicks) => {\n const selectedPicks = Object.entries(singleBetPicks)\n .filter(([, p]) => p.isSelected)\n .map(([k]) => k);\n\n return selectedPicks.length > 0\n ? {\n betslipType: BetslipType.Single,\n pickId: pickIdFactory(selectedPicks[0]),\n }\n : undefined;\n});\n\nexport const selectHasEachWayOptionPick = createSelector(\n betslipPicksListSelector,\n betslipTypeStateSelector,\n (pickList, { base, singleBet, comboBet, systemBet }) => {\n if (!pickList.some(BetslipV2OptionMarketPick.isPick)) {\n return false;\n }\n\n switch (base.currentSelectedType) {\n case BetslipType.Single:\n return pickList.filter(BetslipV2OptionMarketPick.isPick).some((pick) => {\n const pickState = singleBet.picks[pick.id.toString()];\n\n return !!pickState && pickState.isEachWay && pickState.isSelected;\n });\n\n case BetslipType.Combo:\n return comboBet.isEachWay;\n case BetslipType.System:\n return systemBet.isEachWay;\n default:\n return false;\n }\n },\n);\n\nexport const selectRewardTokensOnboardingContext = createSelector(\n selectIsLinearBetslip,\n selectSingleBetStandardRewardTokensContext,\n betslipCurrentTypeSelector,\n (isLinear, singleBetRewardTokensContext, currentType) => {\n if (isLinear) {\n return singleBetRewardTokensContext;\n }\n\n switch (currentType) {\n case BetslipType.Combo:\n return {\n betslipType: BetslipType.Combo,\n };\n\n case BetslipType.Single:\n return singleBetRewardTokensContext;\n\n case BetslipType.Teaser:\n return {\n betslipType: BetslipType.Teaser,\n };\n\n default:\n return undefined;\n }\n },\n);\n\nexport const selectRewardTokensOnboardingEligibility = createSelector(\n selectTokensEligibilityState,\n selectRewardTokensOnboardingContext,\n (eligibility, context) => {\n return context ? getEligibilityStateForContext(context, eligibility) : [];\n },\n);\n\nexport const selectRewardTokensOnboardingEligibileTokens = createSelector(\n selectRewardTokensOnboardingEligibility,\n selectRewardTokens,\n (eligibility, tokens) => {\n return eligibility.filter((e) => e.isEligible).map((e) => tokens[e.tokenId]);\n },\n);\n\nexport const selectAcquisitionRewardToken = createSelector(selectRewardTokensList, (tokens) => getAcquisitionRewardToken(tokens));\nexport const selectRewardTokensOnboardingRewardSelectorVisible = createSelector(\n selectHasEachWayOptionPick,\n selectHasNewCustomerOfferPick,\n selectRewardTokensOnboardingEligibileTokens,\n (hasEachWayOption, hasNewCustomerOffer, tokens) => !hasEachWayOption && !hasNewCustomerOffer && tokens.length > 0,\n);\n\nexport const selectRewardTokensOnboardingDisplayState = createSelector(\n rewardTokensOnBoardingSelector,\n selectRewardTokensOnboardingEligibileTokens,\n selectRewardTokensOnboardingRewardSelectorVisible,\n selectAcquisitionRewardToken,\n (onboarding, onboardingTokens, rewardsSelectorVisible, acquisitionRewardToken) => {\n return { isVisible: onboarding.visible && rewardsSelectorVisible && !acquisitionRewardToken, tokens: onboardingTokens };\n },\n);\n\nexport const selectAcquisitionRewardOnboardingDisplayState = createSelector(\n rewardTokensOnBoardingSelector,\n selectRewardTokensOnboardingRewardSelectorVisible,\n (onboarding, rewardsSelectorVisible) => ({\n acquisitionHidden: onboarding.acquisitionHidden && rewardsSelectorVisible,\n }),\n);\n\nexport const selectHasAcquisitionRewardOnboardingForContext = (context: RewardTokenContext) => {\n return createSelector(selectSelectedTokenForContext(context), selectAcquisitionRewardOnboardingDisplayState, (selectedToken, displayState) => {\n return selectedToken?.rewardTargetType === RewardTargetType.WelcomeOffer && !displayState.acquisitionHidden;\n });\n};\n\nexport const selectBetstationTokensState = createSelector(rewardTokensStateSelector, (state) => state.betstationTokens);\nexport const selectEdsTokensState = createSelector(selectBetstationTokensState, (state) => state.edsState);\nexport const selectSelectedEdsTokens = createSelector(selectEdsTokensState, (state) => state.selectedTokenIds);\nexport const selectBogTokensState = createSelector(selectBetstationTokensState, (state) => state.bogTokenState);\n\nexport const selectSelectedBogToken = createSelector(selectBogTokensState, selectRewardTokens, (bogState, tokens) =>\n getSelectedBogToken(bogState, tokens),\n);\n\nexport const selectHasBogTokenSelected = createSelector(selectBogTokensState, (bogState) => !!bogState.selectedTokenId);\n\nexport const selectBogTokenContext = createSelector(rewardTokensStateSelector, (state) => getBestOddsGuaranteedTokensContext(state));\n\nexport const selectHasBetstationFreeBetSelected = createSelector(\n selectBetstationTokensState,\n (extended) => !!extended.betStationFreeBet.selectedTokenId,\n);\n\nexport const selectHasEdsTokenSelected = createSelector(selectEdsTokensState, (edsState) => edsState.selectedTokenIds.length > 0);\n\nexport const selectSelectedBetstationFreeBetToken = createSelector(selectBetstationTokensState, selectRewardTokens, (betstationState, tokens) =>\n getSelectedBetStationFreeBetToken(betstationState, tokens),\n);\n\nexport const selectSingleBetPickRewardTokenFactory = (pickId: PickId) =>\n createSelector(singleBetPickSelectorFactory(pickId), selectRewardTokens, (pick, tokens) => {\n if (!pick) {\n return null;\n }\n\n return pick.rewardTokenId ? tokens[pick.rewardTokenId] : null;\n });\n\nexport const selectSelectedFreeBetTokenForContextFactory = (context: RewardTokenContext) =>\n createSelector(selectTokenForContextFactory(context), (token) => {\n return isFreebetToken(token) ? token : null;\n });\n\nexport const selectHasComboPreventionForContextFactory = (context: RewardTokenContext) => {\n return createSelector(\n selectHasComboPreventionForTypeFactory(context.betslipType!),\n (hasComboPrevention) => hasComboPrevention && context.betslipType !== BetslipType.Single,\n );\n};\n\nexport const selectMultiSingleTokenRewardContext = createSelector(betslipCurrentTypeSelector, singleBetPicksSelector, (type, picks) => {\n if (type !== BetslipType.Single) {\n return undefined;\n }\n\n const selectedPicks = Object.entries(picks).filter(([_, pick]) => pick.isSelected);\n\n const picksWithTokens = selectedPicks.filter(([, pick]) => !!pick.rewardTokenId);\n const singleTokenSelected = picksWithTokens.length === 1;\n\n return singleTokenSelected\n ? {\n betslipType: BetslipType.Single,\n pickId: pickIdFactory(picksWithTokens[0][0]),\n }\n : undefined;\n});\n\nexport const selectRecoverableTokens = createSelector(rewardTokensStateSelector, (tokens) => tokens.tokenRecoveryState);\n\nexport const selectCurrentEligibilityState = createSelector(\n selectTokensEligibilityState,\n betslipCurrentTypeSelector,\n selectSingleBetSinglePickId,\n (eligibilities, currentType, singleBetSinglePickId) => {\n switch (currentType) {\n case BetslipType.Single:\n if (!singleBetSinglePickId) {\n return [];\n }\n\n return eligibilities.singleBet[singleBetSinglePickId] ?? [];\n\n case BetslipType.Combo:\n return eligibilities.comboBet;\n\n case BetslipType.Teaser:\n return eligibilities.teaserBet;\n\n default:\n return [];\n }\n },\n);\n\nexport const selectAcquisitionSelectionTokenContext = createSelector(betslipTypeStateSelector, selectRewardTokensList, (types, tokens) => {\n const acquisitionRewardToken = getAcquisitionRewardToken(tokens);\n if (!acquisitionRewardToken) {\n return;\n }\n const acquisitionSelectionTokenContext = getTokenSelectionContext(acquisitionRewardToken.userTokenId, types);\n\n return { userTokenId: acquisitionRewardToken.userTokenId, ...acquisitionSelectionTokenContext };\n});\n\nexport const selectShowRewardsNotEligibleMessage = createSelector(\n selectIsLinearBetslip,\n betslipCurrentTypeSelector,\n selectNonAccaTokens,\n (isLinear, betslipCurrentType, tokens) => !isLinear && tokens.length > 0 && betslipCurrentType === BetslipType.System,\n);\n\nexport const selectHasEachWayOptionPickForContext = (context: RewardTokenContext) => {\n return createSelector(betslipPicksListSelector, betslipTypeStateSelector, (pickList, { singleBet, comboBet, systemBet }) => {\n if (!pickList.some(BetslipV2OptionMarketPick.isPick)) {\n return false;\n }\n\n switch (context.betslipType) {\n case BetslipType.Single:\n const pickId = context.pickId?.toString();\n\n if (!pickId) {\n return false;\n }\n\n const pick = pickList.find((p) => p.id.toString() === pickId);\n\n const pickState = singleBet.picks[pickId];\n\n if (!pick || !pickState) {\n return false;\n }\n\n return pick && pickState && BetslipV2OptionMarketPick.isPick(pick) && pickState.isEachWay && pickState.isSelected;\n case BetslipType.Combo:\n return comboBet.isEachWay;\n case BetslipType.System:\n return systemBet.isEachWay;\n default:\n return false;\n }\n });\n};\n\nexport const selectIsRewardTokensSelectorVisible = (context: RewardTokenContext) => {\n return createSelector(\n selectHasEachWayOptionPickForContext(context),\n selectHasNewCustomerOfferPick,\n selectHasVisibleTokensForContext(context),\n selectHasComboPreventionForContextFactory(context),\n (eachWayOptionPick, newCustomerOffer, hasVisibleTokens, hasComboPrevention) => {\n return hasVisibleTokens && !newCustomerOffer && !eachWayOptionPick && !hasComboPrevention;\n },\n );\n};\n\nexport const selectRewardsTokensSelectorState = (context: RewardTokenContext) =>\n createSelector(\n selectIsRewardTokensSelectorVisible(context),\n selectTokenAndEligibilityFactory(context),\n selectHasAcquisitionRewardOnboardingForContext(context),\n (isVisible, [selectedToken, eligibility], hasAcquisitionOnboarding) => ({\n isVisible,\n selectedToken,\n isTokenInvalid:\n !eligibility || !!eligibility.invalidHardCriteria || !!Object.values(eligibility.softCriteriasValidity).some((x) => x === false),\n hasAcquisitionOnboarding,\n }),\n );\n","import { createSelector } from '@ngrx/store';\n\nimport { BetslipType } from '../../../../common/betslip/core/betslip-type';\nimport { selectTokensStateContext } from '../../../../common/betslip/modules/reward-tokens/selectors';\nimport { getSelectedAndEligibleAccaBoost } from '../../../../common/betslip/modules/reward-tokens/services/linear-reward-tokens.utils';\nimport { betslipCurrentTypeSelector } from '../../../../common/betslip/modules/types/base/selectors';\n\nexport const accaBoostTokenSelector = createSelector(selectTokensStateContext, ({ eligibilityState, tokens, types }) => {\n return getSelectedAndEligibleAccaBoost(eligibilityState, tokens, types);\n});\n\nexport const accaBoostTokenForCurrentTypeSelector = createSelector(accaBoostTokenSelector, betslipCurrentTypeSelector, (accaBoost, type) => {\n return type && type === BetslipType.Combo ? accaBoost : null;\n});\n\nexport const isAccaBoostTokenSelector = createSelector(accaBoostTokenSelector, (token) => !!token);\n","import { Injectable, inject } from '@angular/core';\n\nimport { BetslipBarConfig } from '@frontend/sports/common/client-config-data-access';\nimport { RouteTag } from '@frontend/sports/common/core/data-access/route';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { SportsProductService } from '@frontend/sports/host-app/sports-product/feature/sports-product-service';\nimport { DslService, HtmlNode } from '@frontend/vanilla/core';\nimport { Store } from '@ngrx/store';\nimport { BehaviorSubject, combineLatest, map, startWith, tap } from 'rxjs';\n\nimport { betslipPicksListCountSelector } from '../../picks/selectors';\n\n@Injectable({ providedIn: 'root' })\nexport class BetslipBarVisibilityService {\n private readonly visibilitySubject = new BehaviorSubject(true);\n private readonly isSportsActive$ = inject(SportsProductService).isSportsProductActive$;\n\n private readonly BETSLIP_BAR_VISIBLE_CLASS = 'betslip-bar-visible';\n\n private readonly htmlNodeService = inject(HtmlNode);\n private readonly betslipBarConfig = inject(BetslipBarConfig);\n\n private readonly hasPicks$ = inject(Store)\n .select(betslipPicksListCountSelector)\n .pipe(\n startWith(0),\n map((count) => count > 0),\n );\n\n private readonly excludedRoutes$ = inject(RouterEventsService).currentActivationEnd.pipe(\n map((route) => route?.snapshot.data?.tag === RouteTag.MyBets),\n );\n\n readonly enableDsl$ = inject(DslService).evaluateExpression(this.betslipBarConfig.isEnabledDsl);\n\n readonly componentVisible$ = combineLatest([\n this.visibilitySubject,\n this.enableDsl$,\n this.excludedRoutes$,\n this.hasPicks$,\n this.isSportsActive$,\n ]).pipe(\n map(\n ([isVisible, enabled, onExcludedRoute, hasPicks, isSportsActive]) =>\n isSportsActive && enabled && isVisible && !onExcludedRoute && (!this.betslipBarConfig.hideOnEmptyBetslip || hasPicks),\n ),\n tap((visible) => {\n this.htmlNodeService.setCssClass(this.BETSLIP_BAR_VISIBLE_CLASS, visible);\n }),\n );\n\n hideComponent(): void {\n this.visibilitySubject.next(false);\n }\n\n showComponent(): void {\n this.visibilitySubject.next(true);\n }\n}\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector } from '../../base/store/selectors';\n\nexport const hiddenMarketStateSelector = createSelector(betslipSelector, (state) => state.hiddenMarket);\nexport const hasPlacedFirstBetSelector = createSelector(hiddenMarketStateSelector, (state) => state.hasPlacedFirstBet);\n","import { MessageEnvelope } from '@cds/push';\nimport { BetslipDisplayMode } from '@frontend/sports/types/components/bet-placement';\nimport { createAction, props } from '@ngrx/store';\n\nimport { BetslipState } from '../../core/betslip-state';\n\nexport default class BetslipActions {\n static readonly ACTION_SCHEMA = '@betslip';\n\n private static readonly SET_STATE = `${BetslipActions.ACTION_SCHEMA}/SET_STATE`;\n private static readonly EVENT_UPDATE = `${BetslipActions.ACTION_SCHEMA}/EVENT_UPDATE`;\n private static readonly FIXTURE_UPDATE = `${BetslipActions.ACTION_SCHEMA}/FIXTURE_UPDATE`;\n private static readonly TOGGLE_BETSLIP_DISPLAY_MODE = `${BetslipActions.ACTION_SCHEMA}/TOGGLE_BETSLIP_DISPLAY_MODE`;\n\n static setState = createAction(BetslipActions.SET_STATE, props<{ state: BetslipState }>());\n static eventUpdate = createAction(BetslipActions.EVENT_UPDATE, props<{ message: MessageEnvelope }>());\n static fixtureUpdate = createAction(BetslipActions.FIXTURE_UPDATE, props<{ message: MessageEnvelope }>());\n static betBuilderGroupUpdate = createAction(`${BetslipActions.ACTION_SCHEMA}/BETBUILDER_GROUP_UPDATE`, props<{ message: MessageEnvelope }>());\n static toggleBetslipDisplayMode = createAction(BetslipActions.TOGGLE_BETSLIP_DISPLAY_MODE, props<{ mode: BetslipDisplayMode }>());\n}\n","import { OddsAcceptanceMode } from '@bpos';\nimport { createAction, props } from '@ngrx/store';\n\nimport BetslipActions from '../../base/store/actions';\n\nexport default class SettingsActions {\n static readonly ACTION_SCHEMA = `${BetslipActions.ACTION_SCHEMA}/settings`;\n\n private static readonly REQUEST_SET_ODD_ACCEPTANCE = `${SettingsActions.ACTION_SCHEMA}/REQUEST_SET_ODD_ACCEPTANCE`;\n private static readonly SET_ODD_ACCEPTANCE = `${SettingsActions.ACTION_SCHEMA}/SET_ODD_ACCEPTANCE`;\n\n private static readonly REQUEST_SET_APP_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/REQUEST_SET_APP_NOTIFY`;\n private static readonly SET_APP_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/SET_APP_NOTIFY`;\n\n private static readonly REQUEST_SET_EMAIL_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/REQUEST_SET_EMAIL_NOTIFY`;\n private static readonly SET_EMAIL_NOTIFY = `${SettingsActions.ACTION_SCHEMA}/SET_EMAIL_NOTIFY`;\n private static readonly UPDATE_NOTIFICATION_SETTINGS = `${SettingsActions.ACTION_SCHEMA}/UPDATE_NOTIFICATION_SETTINGS`;\n\n /**\n * We can set odds acceptance if user is authenticated if not then we redirect to login.\n */\n static requestSetOddAcceptance = createAction(SettingsActions.REQUEST_SET_ODD_ACCEPTANCE, props<{ acceptance: OddsAcceptanceMode }>());\n static setOddAcceptance = createAction(SettingsActions.SET_ODD_ACCEPTANCE, props<{ acceptance: OddsAcceptanceMode }>());\n\n static requestSetAppNotify = createAction(SettingsActions.REQUEST_SET_APP_NOTIFY, props<{ value: boolean }>());\n static setAppNotify = createAction(SettingsActions.SET_APP_NOTIFY, props<{ value: boolean }>());\n\n static requestSetEmailNotify = createAction(SettingsActions.REQUEST_SET_EMAIL_NOTIFY, props<{ value: boolean }>());\n static setEmailNotify = createAction(SettingsActions.SET_EMAIL_NOTIFY, props<{ value: boolean }>());\n\n static updateNotificationSettings = createAction(\n SettingsActions.UPDATE_NOTIFICATION_SETTINGS,\n props<{ oddsAcceptance?: OddsAcceptanceMode; emailNotify?: boolean; appNotify?: boolean }>(),\n );\n}\n","import { PlaceBetRequest } from '@bpos/v2/bet-placement';\nimport { MessageEnvelope } from '@cds/push';\nimport { Nullable } from '@frontend/sports/common/core/utils/extended-types';\nimport { pprops } from '@frontend/sports/common/core/utils/redux';\nimport { createAction, props } from '@ngrx/store';\nimport { IEarlyPayoutData } from 'packages/sports/web/app/src/my-bets-base/models/early-payout-types';\nimport { IEarlyPayoutResultedPicks, MyBetsBetSlip } from 'packages/sports/web/app/src/my-bets-base/models/my-bets-viewmodels';\nimport { NumpadAction } from 'packages/sports/web/app/src/numpad/model';\n\nimport BetslipActions from '../../base/store/actions';\nimport { IBetslipState } from '../../base/store/state';\nimport { BetslipPick } from '../../core/picks/betslip-pick';\nimport { PickId, V1PickId, V2OptionMarketPickId, V2OptionMarketXCastPickId, V2ParticipantPickId } from '../../core/picks/pick-id';\nimport { ResultStatus } from '../../core/picks/pick-models';\nimport { EditMyBetPicksPayload, IEditMyBetInitPayload } from '../../model/edit-mybet/edit-bet-init';\nimport { IBetPicks } from '../betplacement/models';\nimport { IEditMyBetPickState } from './state';\n\nexport interface IFillEditMyBetPayload {\n pickState: { [pos: number]: { pick: BetslipPick; state: IEditMyBetPickState } };\n sourceBetslip: MyBetsBetSlip;\n}\n\nexport type IEditBetUpdatedEarlyPayout = IEarlyPayoutData & IEarlyPayoutResultedPicks;\n\nexport interface IEditBetUpdatedPayload {\n earlyPayout: Nullable;\n v1FixtureMessages: MessageEnvelope[]; // We can combine them in one collection at some point of time.\n v2FixtureMessages: MessageEnvelope[];\n}\n\nexport interface IPlaceBetPayload {\n requestId: string;\n}\n\nexport interface IPlaceBetResponsePayload {\n guid: string;\n requestPicks: IBetPicks;\n state: IBetslipState;\n timeout: Date;\n placeBetRequest: PlaceBetRequest;\n}\n\nexport interface IEditBetPickResultsPayload {\n pickId: PickId;\n newState: ResultStatus;\n oldState: ResultStatus;\n}\n\nexport interface ISwapPickPayload {\n oldPick: BetslipPick;\n newPick: BetslipPick;\n highlightNewPick: boolean;\n}\n\nexport class EditBetActions {\n static readonly ACTION_SCHEMA = `${BetslipActions.ACTION_SCHEMA}/edit-bet`;\n\n private static readonly INIT = `${EditBetActions.ACTION_SCHEMA}/INIT`;\n private static readonly SET_DEFAULT = `${EditBetActions.ACTION_SCHEMA}/SET_DEFAULT`;\n private static readonly FILL = `${EditBetActions.ACTION_SCHEMA}/FILL`;\n private static readonly SWAP_EDIT_BET_PICK = `${EditBetActions.ACTION_SCHEMA}/SWAP_EDIT_BET_PICK`;\n\n private static readonly START_ADDING_STAKE = `${EditBetActions.ACTION_SCHEMA}/START_ADDING_STAKE`;\n private static readonly CONFIRM_ADDING_STAKE = `${EditBetActions.ACTION_SCHEMA}/CONFIRM_ADDING_STAKE`;\n private static readonly CANCEL_ADDING_STAKE = `${EditBetActions.ACTION_SCHEMA}/CANCEL_ADDING_STAKE`;\n private static readonly SET_ADDED_STAKE = `${EditBetActions.ACTION_SCHEMA}/SET_ADDED_STAKE`;\n\n private static readonly DEPOSIT = `${EditBetActions.ACTION_SCHEMA}/DEPOSIT`;\n private static readonly MAKE_DEPOSIT = `${EditBetActions.ACTION_SCHEMA}/MAKE_DEPOSIT`;\n\n private static readonly ADD_PICK = `${EditBetActions.ACTION_SCHEMA}/ADD_PICK`;\n private static readonly REMOVE_PICK = `${EditBetActions.ACTION_SCHEMA}/REMOVE_PICK`;\n private static readonly DELETE_PICK = `${EditBetActions.ACTION_SCHEMA}/DELETE_PICK`;\n private static readonly UNDO_REMOVE_PICK = `${EditBetActions.ACTION_SCHEMA}/UNDO_REMOVE_PICK`;\n private static readonly START_UNDOING_REMOVE_PICK = `${EditBetActions.ACTION_SCHEMA}/START_UNDOING_REMOVE_PICK`;\n private static readonly REQUEST_DISCARD = `${EditBetActions.ACTION_SCHEMA}/REQUEST_DISCARD`;\n private static readonly CANCEL_DISCARD_REQUEST = `${EditBetActions.ACTION_SCHEMA}/CANCEL_DISCARD_REQUEST`;\n private static readonly EXIT_EDIT_BET = `${EditBetActions.ACTION_SCHEMA}/EXIT_EDIT_BET`;\n private static readonly SWITCH_TO_ADD_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/SWITCH_TO_ADD_SELECTION_MODE`;\n private static readonly TOGGLE_PICKS_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/TOGGLE_PICKS_CONTAINER`;\n private static readonly CANCEL_ADD_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/CANCEL_ADD_SELECTION_MODE`;\n private static readonly CONFIRM_ADD_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/CONFIRM_ADD_SELECTION_MODE`;\n private static readonly START_ADDING_PICK_TO_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/START_ADDING_PICK_TO_CONTAINER`;\n private static readonly ADD_PICK_TO_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/ADD_PICK_TO_CONTAINER`;\n private static readonly REMOVE_PICK_FROM_CONTAINER = `${EditBetActions.ACTION_SCHEMA}/REMOVE_PICK_FROM_CONTAINER`;\n private static readonly SET_FIXED_PRICE = `${EditBetActions.ACTION_SCHEMA}/SET_FIXED_PRICE`;\n private static readonly SET_STARTING_PRICE = `${EditBetActions.ACTION_SCHEMA}/SET_STARTING_PRICE`;\n private static readonly SWITCH_TO_SWAP_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/SWITCH_TO_SWAP_SELECTION_MODE`;\n private static readonly START_ADDING_PICK_TO_SWAP = `${EditBetActions.ACTION_SCHEMA}/START_ADDING_PICK_TO_SWAP`;\n private static readonly ADD_PICK_TO_SWAP = `${EditBetActions.ACTION_SCHEMA}/ADD_PICK_TO_SWAP`;\n private static readonly CANCEL_SWAP_SELECTION_MODE = `${EditBetActions.ACTION_SCHEMA}/CANCEL_SWAP_SELECTION_MODE`;\n private static readonly UPDATE_PICK_TO_SWAP = `${EditBetActions.ACTION_SCHEMA}/UPDATE_PICK_TO_SWAP`;\n private static readonly HANDLE_SWAP_ERROR = `${EditBetActions.ACTION_SCHEMA}/HANDLE_SWAP_ERROR`;\n private static readonly PLACE_BET = `${EditBetActions.ACTION_SCHEMA}/PLACE_BET`;\n private static readonly EARLY_PAYOUT_UPDATED = `${EditBetActions.ACTION_SCHEMA}/EARLY_PAYOUT_UPDATED`;\n private static readonly EDIT_BET_UPDATED = `${EditBetActions.ACTION_SCHEMA}/EDIT_BET_UPDATED`;\n private static readonly GET_PLACE_BET_STATUS = `${EditBetActions.ACTION_SCHEMA}/GET_PLACE_BET_STATUS`;\n private static readonly ACCEPT_CURRENT_EARLY_PAYOUT_VALUE = `${EditBetActions.ACTION_SCHEMA}/ACCEPT_CURRENT_EARLY_PAYOUT_VALUE`;\n private static readonly DISABLE_EARLY_PAYOUT = `${EditBetActions.ACTION_SCHEMA}/DISABLE_EARLY_PAYOUT`;\n private static readonly APPLY_EDIT_BET_PICK_RESULTS = `${EditBetActions.ACTION_SCHEMA}/APPLY_EDIT_BET_PICK_RESULTS`;\n private static readonly START_EDIT_MY_BET = `${EditBetActions.ACTION_SCHEMA}/START_EDIT_MY_BET`;\n private static readonly SUBSCRIBE_TO_EARLY_PAYOUT = `${EditBetActions.ACTION_SCHEMA}/SUBSCRIBE_TO_EARLY_PAYOUT`;\n private static readonly UNSUBSCRIBE_FROM_EARLY_PAYOUT = `${EditBetActions.ACTION_SCHEMA}/UNSUBSCRIBE_FROM_EARLY_PAYOUT`;\n\n /**\n * Init Edit Bet\n */\n static init = createAction(EditBetActions.INIT, pprops());\n\n /**\n * Add new pick to the slip.\n */\n static addPick = createAction(EditBetActions.ADD_PICK, props<{ pick: BetslipPick }>());\n\n /**\n * Fill Edit bet with data\n */\n static fillEditMyBet = createAction(EditBetActions.FILL, pprops());\n\n /**\n * Swap a pick in edit bet.\n */\n static swapPick = createAction(EditBetActions.SWAP_EDIT_BET_PICK, pprops());\n\n /**\n * Start adding top-up stake. Shows the Stake keypad.\n */\n static startAddingStake = createAction(EditBetActions.START_ADDING_STAKE);\n\n /**\n * Notify action when user stopped changing top-up stake. On numpad OK button click.\n * null is canceling of adding stake.\n */\n static confirmAddingStake = createAction(EditBetActions.CONFIRM_ADDING_STAKE, props<{ stake: number }>());\n\n /**\n * Close stake numpad - do nothing.\n */\n static cancelAddingStake = createAction(EditBetActions.CANCEL_ADDING_STAKE);\n\n /**\n * Start adding funds through quick-deposit\n */\n static deposit = createAction(EditBetActions.DEPOSIT);\n\n /**\n * Set current entered stake.\n */\n static setEditMyBetAddedStake = createAction(EditBetActions.SET_ADDED_STAKE, pprops<{ stake: number; action?: NumpadAction }>());\n\n /**\n * Ask for confirmation to leave EMB and redirect to deposit page or open quickDeposit\n */\n static makeDeposit = createAction(EditBetActions.MAKE_DEPOSIT);\n\n /**\n * Removes pick from edit bet (marks it as not used)\n */\n static removePick = createAction(EditBetActions.REMOVE_PICK, props<{ pickId: string }>());\n\n /**\n * Deletes pick from edit bet (marks it as not used)\n */\n static deletePick = createAction(EditBetActions.DELETE_PICK, props<{ pickId: PickId }>());\n\n /**\n * Start process of Undo remove pick. Checks if the betslip is not full.\n */\n static startUndoingRemovePick = createAction(EditBetActions.START_UNDOING_REMOVE_PICK, props<{ pickId: string }>());\n\n /**\n * Undo remove (enable back) the pick in edit bet.\n */\n static undoRemovePick = createAction(EditBetActions.UNDO_REMOVE_PICK, props<{ pickId: string }>());\n\n /**\n * User tries to exit edit bet (Clicks on Cancel button).\n */\n static requestDiscard = createAction(EditBetActions.REQUEST_DISCARD);\n\n /**\n * User cancels his/her request to edit bet. Decides to continue editing\n */\n static cancelDiscardRequest = createAction(EditBetActions.CANCEL_DISCARD_REQUEST);\n\n /**\n * User exits edit bet mode.\n */\n static exitEditBet = createAction(EditBetActions.EXIT_EDIT_BET);\n\n /**\n * Set default edit bet\n */\n static setDefault = createAction(EditBetActions.SET_DEFAULT);\n\n /**\n * User start adding picks to edit bet.\n */\n static switchToAddSelectionMode = createAction(EditBetActions.SWITCH_TO_ADD_SELECTION_MODE);\n\n /**\n * Toggles the visibility of edit bet picks staging area. Show/Hides the pick list.\n */\n static togglePicksContainer = createAction(EditBetActions.TOGGLE_PICKS_CONTAINER);\n\n // /**\n // * Cancel adding new picks to edit bet.\n // */\n // static cancelAddSelectionMode = createAction(EditBetActions.CANCEL_ADD_SELECTION_MODE, pprops());\n /**\n * Cancel adding new picks to edit bet.\n */\n static cancelAddSelectionMode = createAction(EditBetActions.CANCEL_ADD_SELECTION_MODE, pprops());\n\n /**\n * Adds the staging picks to edit bet.\n */\n static confirmAddSelectionMode = createAction(EditBetActions.CONFIRM_ADD_SELECTION_MODE, pprops<{ picks: BetslipPick[] }>());\n\n /**\n * Start adding pick to staging area. Creates request to BCD to resolve pick data.\n */\n static startAddingPickToContainer = createAction(\n EditBetActions.START_ADDING_PICK_TO_CONTAINER,\n props<{ pickId: V1PickId | V2OptionMarketPickId | V2ParticipantPickId | V2OptionMarketXCastPickId }>(),\n );\n\n /**\n * Add pick to edit bet staging area. After we have bcd data.\n */\n static addPickToContainer = createAction(EditBetActions.ADD_PICK_TO_CONTAINER, props<{ pick: BetslipPick }>());\n\n /**\n * Remove pick from staging area.\n */\n static removeFromContainer = createAction(EditBetActions.REMOVE_PICK_FROM_CONTAINER, props<{ pickId: string }>());\n\n /**\n * Set fixed price for pick\n */\n static setFixedPrice = createAction(EditBetActions.SET_FIXED_PRICE, props<{ pickId: PickId }>());\n\n /**\n * Set starting price for pick\n */\n static setStartingPrice = createAction(EditBetActions.SET_STARTING_PRICE, props<{ pickId: PickId }>());\n\n /**\n * Start swapping pick. Shows swap pick dialog.\n */\n static switchToSwapSelectionMode = createAction(EditBetActions.SWITCH_TO_SWAP_SELECTION_MODE, props<{ pick: BetslipPick }>());\n\n /**\n * Cancel swapping pick. Closes the swap pick dialog.\n */\n static cancelSwapSelectionMode = createAction(EditBetActions.CANCEL_SWAP_SELECTION_MODE);\n\n /**\n * Select pick to swap. Creates request to BCD to resolve pick data.\n */\n static startAddingPickToSwap = createAction(EditBetActions.START_ADDING_PICK_TO_SWAP, props<{ pick: BetslipPick }>());\n\n /**\n * Swap temporary pick.\n */\n static addPickToSwap = createAction(EditBetActions.ADD_PICK_TO_SWAP, props<{ pick: BetslipPick }>());\n\n static updatePickToSwap = createAction(EditBetActions.UPDATE_PICK_TO_SWAP, props<{ pick: BetslipPick }>());\n\n static handleSwapError = createAction(EditBetActions.HANDLE_SWAP_ERROR, props<{ error: string; pickId?: PickId }>());\n\n /**\n * Place edit bet.\n */\n static placeBet = createAction(EditBetActions.PLACE_BET, pprops());\n\n /**\n * Get current place edit bet status.\n */\n static getPlaceBetStatus = createAction(EditBetActions.GET_PLACE_BET_STATUS, pprops());\n\n /**\n * Action fired when Early payout is updated. Has push message regarding Early payout\n */\n static earlyPayoutUpdated = createAction(EditBetActions.EARLY_PAYOUT_UPDATED, pprops>());\n\n /**\n * Action that modifies the Edit bet (Changes Odds, Pick visibility, Early payout value)\n */\n static editBetUpdated = createAction(EditBetActions.EDIT_BET_UPDATED, pprops());\n\n /**\n * Accept current early payout value\n */\n static acceptCurrentEarlyPayoutValue = createAction(EditBetActions.ACCEPT_CURRENT_EARLY_PAYOUT_VALUE);\n\n /**\n * Disable early payout because of error\n */\n static disableEarlyPayout = createAction(EditBetActions.DISABLE_EARLY_PAYOUT);\n\n static applyEditBetPickResults = createAction(EditBetActions.APPLY_EDIT_BET_PICK_RESULTS, pprops());\n\n /**\n * Subscribe to Early Payout\n */\n static subscribeToEarlyPayout = createAction(EditBetActions.SUBSCRIBE_TO_EARLY_PAYOUT);\n\n /**\n * Unsubscribe from Early payout.\n */\n static unsubscribeFromEarlyPayout = createAction(EditBetActions.UNSUBSCRIBE_FROM_EARLY_PAYOUT);\n\n /**\n * Action called when bet editing is started\n */\n static startEditMyBet = createAction(EditBetActions.START_EDIT_MY_BET, props<{ payload: EditMyBetPicksPayload }>());\n}\n\n// @ts-ignore\nwindow.editBetAction = EditBetActions;\n","import { InjectionToken } from '@angular/core';\n\nimport { BetslipModuleLoaderService } from './betslip-module-loader.service';\nimport IOddsSettingsService from './services/odds-settings.service';\n\nexport const BETSLIP_MODULE_LOADER_SERVICE = new InjectionToken('Betslip module loader token');\n\n/* Services */\n\nexport const ODDS_SETTINGS_SERVICE = new InjectionToken('BET_STATION_ODDS_SETTINGS_SERVICE');\n\nexport const ODDS_VIEW_MODE = new InjectionToken('ODDS_VIEW_MODE');\n","import { Location } from '@angular/common';\nimport { Inject, Injectable } from '@angular/core';\n\nimport { OddsAcceptanceMode } from '@bpos';\nimport { DispatcherService } from '@frontend/sports/common/core/utils/dispatcher';\nimport { NonRootToken } from '@frontend/sports/host-app/sports-product/feature/non-root-token';\nimport { filterSports } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { UrlService } from '@frontend/vanilla/core';\nimport { Store, createSelector } from '@ngrx/store';\nimport { isAccaBoostTokenSelector } from 'packages/sports/web/app/src/acca-boost-base/acca-boost-utils';\nimport { AdaptiveLayoutService } from 'packages/sports/web/app/src/layout/adaptive-layout.service';\nimport { MarketUrlParam } from 'packages/sports/web/app/src/navigation-core/url-helper.service';\nimport StoragePersister from 'packages/sports/web/app/src/store-persist/storage-persister';\nimport { Observable, Subject, combineLatest, of } from 'rxjs';\nimport { distinctUntilChanged, distinctUntilKeyChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';\n\nimport { IBetslipRootState, IBetslipState, betslipFeatureKey } from '../base/store/state';\nimport { BetslipState } from '../core/betslip-state';\nimport { BetslipType } from '../core/betslip-type';\nimport { BetslipHost, ExternalBetslipActions, PickAddPayload } from '../core/external-betslip-actions';\nimport { BetslipBetBuilderPick } from '../core/picks/betslip-bet-builder-pick';\nimport { BetBuilderPickId, PickId } from '../core/picks/pick-id';\nimport { PickType, PriceType } from '../core/picks/pick-models';\nimport { flattenGroupPicksIfAny } from '../core/utils';\nimport { BetslipBarVisibilityService } from '../modules/betslip-bar/services/betslip-bar-visibility.service';\nimport { hasPlacedFirstBetSelector } from '../modules/hidden-market/hidden-market.selectors';\nimport { IRewardToken } from '../modules/reward-tokens/reward-tokens.model';\nimport { selectRewardTokensList } from '../modules/reward-tokens/selectors';\nimport SettingsActions from '../modules/settings/actions';\nimport { EditBetActions } from './../modules/edit-bet/actions';\nimport { BetslipModule, BetslipModuleLoaderService } from './betslip-module-loader.service';\nimport { BETSLIP_MODULE_LOADER_SERVICE } from './sports-injection-services';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class BetslipIntegrationService {\n private betslipHost?: BetslipHost;\n\n private betslipPickSelectedSelectorFactory = (\n module: BetslipModule,\n pickId: PickId,\n priceType?: PriceType,\n longId?: string,\n siblingPickId?: PickId,\n ) =>\n createSelector(\n module.exported.selectors.betslipTypeCurrentTypeSelector,\n module.exported.selectors.betslipPicksListSelector,\n module.exported.selectors.editBetPicksListSelector,\n module.exported.selectors.editBetAddedPicksListSelector,\n module.exported.selectors.editBetPickStateSelector,\n (type, picks, editBetPicks, editBetAddedPicks, pickstate) => {\n if (type === BetslipType.EditBet) {\n const editBetPick =\n editBetPicks.find((p) => p.id.toString() === pickId.toString() && pickstate[pickId.toString()].isRemoved === false) ||\n editBetAddedPicks.find((p) => p.id.toString() === pickId.toString());\n\n return !!editBetPick && (editBetPick.priceType === priceType || priceType === undefined);\n }\n\n if (pickId.getPickType() === PickType.BetBuilderPick && longId) {\n const bbPickId = pickId as BetBuilderPickId;\n\n return picks.some(\n (p) =>\n BetslipBetBuilderPick.isEntainUiBetBuilderPick(p) &&\n p.eventId === bbPickId.eventId &&\n p.sportcastOptions.map((so) => so.longId).some((id) => id === longId),\n );\n }\n\n const pick = flattenGroupPicksIfAny(picks).find((p) => p.id.isEqual(pickId));\n const siblingPick = siblingPickId && picks.find((t) => t.id.isEqual(siblingPickId));\n\n return (!!pick && (pick.priceType === priceType || priceType === undefined)) || !!siblingPick;\n },\n );\n\n private locationUrl$: Observable;\n\n constructor(\n private store: Store,\n private dispatcher: DispatcherService,\n private storagePersister: StoragePersister,\n private adaptiveLayoutService: AdaptiveLayoutService,\n private urlService: UrlService,\n private location: Location,\n @Inject(NonRootToken(BETSLIP_MODULE_LOADER_SERVICE)) private betslipModuleLoader: BetslipModuleLoaderService,\n private betslipBarVisibility: BetslipBarVisibilityService,\n ) {\n const locationUrl = new Subject();\n\n this.locationUrl$ = locationUrl.pipe(distinctUntilChanged(), filterSports());\n\n this.location.onUrlChange((url) => locationUrl.next(url));\n this.betslipInitialized$().subscribe();\n }\n\n betslipInitialized$(): Observable {\n if (this.betslipHost) {\n return of(this.betslipHost);\n }\n\n return this.dispatcher.on('MODULE_LOADED').pipe(\n filter((value) => value === 'BetslipBetStationModule' || value === 'BetslipDigitalModule'),\n take(1),\n map((value) => (value === 'BetslipDigitalModule' ? BetslipHost.Digital : BetslipHost.BetStation)),\n tap((host) => {\n this.betslipHost = host;\n }),\n );\n }\n\n isEditBetActive$(betslipId: string): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap(() => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) =>\n this.store.select(module.exported.selectors.betslipTypeStateSelector).pipe(\n map((state) => {\n return state.base.currentSelectedType === BetslipType.EditBet && state.editBet.betslipId === betslipId;\n }),\n ),\n ),\n );\n }\n\n isAccaBoostToken$(): Observable {\n return this.store.select(isAccaBoostTokenSelector);\n }\n\n betslipState$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap(() => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipBaseStateSelector)),\n );\n }\n\n /**\n * Indicates whether the user has placed a bet in the current application run.\n * Used to determine whether to show hidden markets.\n */\n hasPlacedFirstBet$(): Observable {\n return this.store.select(hasPlacedFirstBetSelector);\n }\n\n rewardTokens$(): Observable {\n return this.store.select(selectRewardTokensList);\n }\n\n requestExitEditBet(): void {\n this.store.dispatch(EditBetActions.requestDiscard());\n }\n\n /**\n * Define when to show betstation betslip:\n * 1. Not initialized ? Check if we have something in the savedState and if we have picks there load it.\n * 2. Initialized ? Check current betslip count.\n */\n shouldShowBetslipInBetStation$(): Observable {\n const betslipPicksCountWatcher$ = this.betslipPicksCount$().pipe(map((count) => !!count));\n if (!this.betslipHost) {\n // Betslip not loaded, check storage if has some picks then load it.\n const savedState = this.storagePersister.load();\n if (savedState && betslipFeatureKey in savedState) {\n const betslipState = savedState[betslipFeatureKey] as IBetslipState;\n if (betslipState?.picks?.pickList?.length) {\n return betslipPicksCountWatcher$.pipe(startWith(true));\n }\n }\n }\n\n // if betslip is loaded or no picks in the storage then check picks count.\n return betslipPicksCountWatcher$;\n }\n\n /**\n * Define when to show QuickBet:\n * Show Quick Bet if the layout's quick bet active is set to true.\n */\n shouldShowQuickBet$(): Observable {\n return this.adaptiveLayoutService.stateChange$.pipe(\n distinctUntilKeyChanged('quickBetActive'),\n map((t) => t.quickBetActive),\n );\n }\n\n /**\n * Define when to show Betslip returns messages:\n * 1. when bottom navigation is visible and\n * 2. when the user is not in the Bet Builder tab\n */\n shouldShowBetslipReturnsMessage$(): Observable {\n const isInBetBuilderTab$ = this.betslipInitialized$().pipe(\n switchMap(() => {\n return this.locationUrl$.pipe(\n map((url) => url && this.urlService.parse(url).search.get('market') === MarketUrlParam.BetBuilder),\n startWith(this.urlService.current().search.get('market') === MarketUrlParam.BetBuilder),\n );\n }),\n );\n\n const hasBottomNavigation$ = this.adaptiveLayoutService.stateChange$.pipe(map((state) => !!state.bottomNavigation));\n\n return combineLatest([isInBetBuilderTab$, hasBottomNavigation$]).pipe(\n map(([isInBetBuilderTab, hasBottomNavigation]) => hasBottomNavigation && !isInBetBuilderTab),\n distinctUntilChanged(),\n );\n }\n\n /**\n * Define when to show QuickBetBuilder in Entain UI:\n * Show Quick Bet Builder Drawer if the layout's quick bet active is set to true.\n */\n shouldShowQuickBetBuilder$(): Observable {\n return this.adaptiveLayoutService.stateChange$.pipe(\n distinctUntilKeyChanged('quickBetBuilderActive'),\n map((t) => t.quickBetBuilderActive),\n );\n }\n\n betslipPicksCount$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipPickIdsSelector).pipe(map((picks) => picks.length))),\n );\n }\n\n betslipPicksAndLegsCount$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipPicksListCountSelectorIncludeBetbuilderLegs)),\n );\n }\n\n betslipPickIds$(): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(module.exported.selectors.betslipPickIdsSelector).pipe(map((picks) => picks))),\n );\n }\n\n pickSelected$(pickId: PickId, priceType?: PriceType, longId?: string, siblingPickId?: PickId): Observable {\n return this.betslipInitialized$().pipe(\n mergeMap((_) => this.betslipModuleLoader.loadBetslipModule()),\n mergeMap((module) => this.store.select(this.betslipPickSelectedSelectorFactory(module, pickId, priceType, longId, siblingPickId))),\n );\n }\n\n get betslipBarEnabled(): Observable {\n return this.betslipBarVisibility.enableDsl$;\n }\n\n addMultiplePicks(payload: { picks: PickAddPayload[]; stake?: number }): void {\n this.store.dispatch(ExternalBetslipActions.addMultiplePicks(payload));\n }\n\n addPick(payload: PickAddPayload): void {\n this.store.dispatch(ExternalBetslipActions.addPick(payload));\n }\n\n removeMultiplePicks(payload: { pickIds: PickId[] }): void {\n this.store.dispatch(ExternalBetslipActions.removeMultiplePicks(payload));\n }\n\n setOddsAcceptance(oddsAcceptance: string = OddsAcceptanceMode.No): void {\n if (!this.betslipHost) {\n // If betslip is not initialized, don't do anything when we init betslip we will read what is the current odds acceptance.\n return;\n }\n this.store.dispatch(SettingsActions.setOddAcceptance({ acceptance: oddsAcceptance as OddsAcceptanceMode }));\n }\n}\n","import { Location } from '@angular/common';\nimport { Injectable } from '@angular/core';\n\nimport { AccaBoostConfig, LayoutNavigationConfig, MyBetsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { DialogAnimation, ModalDialogOptions } from '@frontend/sports/modal/feature';\nimport { TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { OpenBetsSummary } from '@frontend/sports/types/models/my-bets';\nimport { UserService } from '@frontend/sports/user/feature';\nimport { BottomNavService, MenuAction, MenuActionsService, NavigationService } from '@frontend/vanilla/core';\nimport { BetslipIntegrationService } from 'packages/sports/common/betslip/integration/betslip-integration.service';\nimport { combineLatest, switchMap } from 'rxjs';\n\nimport { EpcotConfigService } from '../common/epcot-config.service';\nimport { MyBetsSummaryService } from '../my-bets-base/my-bets-summary.service';\nimport { RedirectHelperService } from '../navigation-core/redirect-helper.service';\nimport { ModelPopupTypes, UrlHelperService } from '../navigation-core/url-helper.service';\nimport { PopupManager } from '../popup/popup-manager.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class BottomNavActionsService {\n private static readonly betslipBadgeClass: string = 'badge-primary';\n private static readonly extraIconBadgeClass: string = 'badge-extra-bubble-icon';\n private static readonly myBetsBadgeClass: string = 'badge-primary theme-spot-filled';\n\n private openBetsCount: number;\n private hasLiveOpenBets = false;\n\n constructor(\n private vanillaNavigation: NavigationService,\n private popupManager: PopupManager,\n private trackingService: TrackingService,\n private redirectHelper: RedirectHelperService,\n private location: Location,\n private myBetsSummary: MyBetsSummaryService,\n private userService: UserService,\n private urlHelper: UrlHelperService,\n private betslipIntegrationService: BetslipIntegrationService,\n private bottomNavService: BottomNavService,\n private menuActionsService: MenuActionsService,\n private accaBoostConfig: AccaBoostConfig,\n private layoutNavigationConfig: LayoutNavigationConfig,\n private epcotConfigService: EpcotConfigService,\n private mybetsConfig: MyBetsConfig,\n ) {}\n\n betslipToggled(): void {\n if (this.popupManager.isOpen()) {\n this.trackingService.track(trackingConstants.EVENT_PAGE_VIEW, { [trackingConstants.PAGE_NAME]: 'M2_slip_action_ClosePopup' });\n }\n this.popupManager.open('betslip', undefined);\n }\n\n myBetsToggled(): void {\n const myBetsUrl = this.urlHelper.getMyBetsUrl();\n if (this.location.path() === myBetsUrl) {\n this.redirectHelper.goBack();\n this.myBetsTracking(false);\n } else {\n if (this.userService.isAuthenticated) {\n if (this.epcotConfigService.isShowMyBetsPopup) {\n this.popupManager.updateDialogAnimation(ModelPopupTypes.MyBets, DialogAnimation.SlideInFromBottom);\n const options: ModalDialogOptions = {\n settings: {\n closeAnimation: DialogAnimation.SlideOutFromTop,\n },\n };\n this.popupManager.open(ModelPopupTypes.MyBets, undefined, options);\n } else {\n this.vanillaNavigation.goTo(myBetsUrl);\n }\n } else {\n const returnUrl = this.epcotConfigService.isShowMyBetsPopup ? this.urlHelper.getMyBetPopupUrl() : myBetsUrl;\n this.menuActionsService.invoke(MenuAction.GOTO_LOGIN, '', [undefined, undefined, { returnUrl }]);\n }\n this.myBetsTracking(true);\n }\n }\n\n private myBetsTracking(isOpen: boolean): void {\n if (!isOpen) {\n return;\n }\n\n let pageName: string;\n if (this.hasLiveOpenBets) {\n pageName = 'HIconON_Live';\n } else if (this.openBetsCount > 0) {\n pageName = 'HIcon_Open';\n } else {\n pageName = 'HIcon_Settled';\n }\n this.trackingService.update({ [trackingConstants.PAGE_REFERRING_ACTION]: pageName });\n }\n\n init(): void {\n if (!this.mybetsConfig.isOpenSummaryLoadingRestricted) {\n this.myBetsSummary.summaryUpdate.pipe(filterSportsEmitLast()).subscribe((stats: OpenBetsSummary) => {\n this.hasLiveOpenBets = stats.liveBetsCount > 0;\n this.openBetsCount = stats.openBetsCount;\n\n let betsCount: number | string = 0;\n let badgeClass = BottomNavActionsService.myBetsBadgeClass;\n if (this.mybetsConfig.isShowOpenBetsCount) {\n if (stats.openBetsCount > 99) {\n betsCount = '99+';\n badgeClass = badgeClass.replace('theme-spot-filled', 'open-bets bets-99-plus');\n } else {\n betsCount = stats.openBetsCount;\n badgeClass = badgeClass.replace('theme-spot-filled', 'open-bets');\n }\n } else betsCount = stats.liveBetsCount;\n const myBetsItem = this.layoutNavigationConfig?.bottomNavItemsMapping['mybets'] || 'mybets';\n\n this.bottomNavService.setItemCounter(myBetsItem, betsCount || null, badgeClass);\n });\n }\n\n this.betslipIntegrationService\n .betslipInitialized$()\n .pipe(\n filterSportsEmitLast(),\n switchMap(() =>\n combineLatest([this.betslipIntegrationService.betslipPicksCount$(), this.betslipIntegrationService.isAccaBoostToken$()]),\n ),\n )\n .subscribe(([count, isAccaBoost]) => this.setBetslipCounter(count, isAccaBoost));\n }\n\n private setBetslipCounter(count: number, isAccaBoost: boolean): void {\n this.bottomNavService.setItemCounter(\n 'betslip',\n count || null,\n isAccaBoost ? this.accaBoostBadgeClass : BottomNavActionsService.betslipBadgeClass,\n );\n }\n\n menuToggled(): void {\n this.vanillaNavigation.goToByCommand([{ outlets: { overlay: 'menu' } }], { skipPrimaryOutletGuardsAndResolvers: true });\n }\n\n private get accaBoostBadgeClass(): string {\n return `${BottomNavActionsService.betslipBadgeClass} ${BottomNavActionsService.extraIconBadgeClass} ${this.accaBoostConfig.icon}`;\n }\n}\n","import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';\n\nexport enum GameLauncherSection {\n Gyml = 'gyml',\n Link = 'link',\n Jackpot = 'jackpot',\n}\n\nexport interface GameLauncherModel {\n name: string;\n section: GameLauncherSection;\n position: number;\n title: string;\n provider: string;\n lobbyPosition: string;\n}\n\nexport const gameLauncherResolver: ResolveFn = (route: ActivatedRouteSnapshot): GameLauncherModel => ({\n name: route.params.name,\n section: route.params.section,\n position: route.params.position,\n title: route.params.title,\n provider: route.params.provider,\n lobbyPosition: route.params.lobbyPosition,\n});\n","import { Injectable } from '@angular/core';\n\nimport { LocalStoreService } from '@frontend/vanilla/core';\n\nexport interface StorageItem {\n data: T;\n timestamp: number;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class LocalStorageService {\n constructor(private localStore: LocalStoreService) {}\n\n getItem(storageKey: string): T[] {\n const y = this.localStore.get(storageKey) || [];\n\n return y;\n }\n\n getItemObject(storageKey: string): T | null {\n return this.localStore.get(storageKey);\n }\n\n saveItem(storageKey: string, input: T): void {\n if (!input) {\n return;\n }\n\n this.localStore.set(storageKey, input);\n }\n\n clearStorage(storageKey: string): void {\n this.localStore.remove(storageKey);\n }\n\n addTimestampToItem(item: T): StorageItem {\n const timestamp = new Date().getTime();\n\n return { data: item, timestamp } as StorageItem;\n }\n\n sortStorageItemsByTimestamp(items: StorageItem[]): StorageItem[] {\n return items.sort((a, b) => b.timestamp - a.timestamp);\n }\n\n clearExpiredItems(expirationDays: number, storageKey: string): void {\n const storedItems: StorageItem[] = this.getItem>(storageKey);\n const currentTime = new Date().getTime();\n let hasItemRemoved = false;\n\n if (storedItems.length) {\n const filteredArray = storedItems.filter((item) => {\n if (!item?.timestamp) {\n return false;\n }\n const millisecondsInOneDay: number = 1000 * 60 * 60 * 24;\n const daysDifference: number = Math.floor((currentTime - item.timestamp) / millisecondsInOneDay);\n\n if (daysDifference >= expirationDays) {\n hasItemRemoved = true;\n\n return false;\n }\n\n return true;\n });\n\n if (hasItemRemoved) {\n this.saveItem[]>(storageKey, filteredArray);\n }\n }\n }\n}\n","import { Injectable } from '@angular/core';\nimport { Router } from '@angular/router';\n\nimport { PrettyUrlsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { ParsedUrl, UrlService } from '@frontend/vanilla/core';\nimport { capitalize, cloneDeep, isEqual, toInteger } from 'lodash-es';\n\nimport { CompetitionRoute } from '../navigation/navigation.models';\n\ntype RegExpMapper = (result: RegExpExecArray) => CompetitionRoute;\n\n@Injectable({\n providedIn: 'root',\n})\nexport class CompetitionRouteService {\n private parsedUrl: ParsedUrl;\n private parsedRoute: CompetitionRoute = {};\n private matchers = new Map();\n private mappers = new Map();\n protected get base(): string {\n return '(/.+/sports)';\n }\n\n constructor(\n routerEvents: RouterEventsService,\n private router: Router,\n private urlService: UrlService,\n private urlConfig: PrettyUrlsConfig,\n ) {\n const context = (...options: string[]) => `(/(${options.join('|')}))`;\n const register = (key: string, regex: string, mapper: RegExpMapper) => {\n this.matchers.set(key, new RegExp(regex, 'i'));\n this.mappers.set(key, mapper);\n };\n\n const couponContext = context(this.urlConfig.translations.coupons);\n const competitionContext = context(this.urlConfig.translations.competitions);\n const conferencesContext = context(this.urlConfig.translations.conferences);\n const tournamentContext = context(this.urlConfig.translations.tournaments);\n const teampagesContext = context(this.urlConfig.translations.teamPages);\n const betsContext = '/' + this.urlConfig.translations.bets;\n const standingsContext = '/' + this.urlConfig.translations.standings;\n const liveContext = context(this.urlConfig.translations.live);\n const worldCupHubContext = context(this.urlConfig.translations.worldCupHub);\n\n const baseOptions = [\n this.urlConfig.translations.betting,\n this.urlConfig.translations.calendar,\n this.urlConfig.translations.in30minutes,\n this.urlConfig.translations.in60minutes,\n this.urlConfig.translations.in180minutes,\n this.urlConfig.translations.today,\n this.urlConfig.translations.nextRaces,\n this.urlConfig.translations.tomorrow,\n this.urlConfig.translations.after2days,\n this.urlConfig.translations.after3days,\n this.urlConfig.translations.next2days,\n this.urlConfig.translations.next3days,\n this.urlConfig.translations.next5days,\n this.urlConfig.translations.midWeek,\n this.urlConfig.translations.thisWeekend,\n ];\n const eventContext = context(...baseOptions, this.urlConfig.translations.coupons);\n const baseContext = context(...baseOptions);\n\n const node = '(/(([^/]+)-)?(\\\\d+))'; // name-identifier with optional name\n const virtualNode = '(/(([^/]+)-)?(0:(\\\\d+)))';\n\n register('bets', `${this.base}${betsContext}${node}${node}?${node}?${node}?$`, this.getBetsRoute);\n register('bets-virtual', `${this.base}${betsContext}${node}${node}${virtualNode}$`, this.getBetsVirtualRoute);\n register('sport', `${this.base}${node}${eventContext}?${competitionContext}?$`, this.getSportRoute);\n register('sport-modular', `${this.base}${node}/modular$`, this.getSportRoute); // temporary, needs to be removed after the route switch technique will be included\n register('calendar', `${this.base}${eventContext}$`, this.getCalendarRoute);\n register('conferences', `${this.base}${node}${conferencesContext}${node}?${node}?${node}?$`, this.getConferencesRoute);\n register('betting', `${this.base}${node}${baseContext}${node}?${node}?$`, this.getBettingRoute);\n register('betting-virtual', `${this.base}${node}${baseContext}${node}${virtualNode}${node}?$`, this.getVirtualBettingRoute);\n register('live', `${this.base}${liveContext}${node}${node}?${node}?$`, this.getLiveRoute);\n register('live-conference', `${this.base}${liveContext}${node}${node}?${node}?${node}?$`, this.getLiveConferenceRoute);\n register('live-virtual', `${this.base}${liveContext}${node}${node}${virtualNode}${node}?$`, this.getVirtualLiveRoute);\n register('betting-multi', `${this.base}${node}${baseContext}/(\\\\d+(,\\\\d+)*)$`, this.getBettingSportRoute);\n register('coupon', `${this.base}${node}${couponContext}(/${this.urlConfig.translations.betBuilder}|${node})?$`, this.getCouponRoute);\n register('tournament', `${this.base}${node}${tournamentContext}(/([^/]+))?${node}?$`, this.getTournamentRoute);\n register('teamPages', `${this.base}${node}${teampagesContext}(/([^/]+))?${node}?$`, this.getCommon);\n register('standings', `${this.base}${standingsContext}${node}${node}${node}?$`, this.getStandingsRoute);\n register('standings-virtual', `${this.base}${standingsContext}${node}${node}${virtualNode}${node}?$`, this.getStandingsVirtualRoute);\n register(\n 'coupon-competition',\n `${this.base}${node}${couponContext}${node}${node}(/${this.urlConfig.translations.betBuilder}|${node})$`,\n this.getCouponCompetitionRoute,\n );\n register(\n 'coupon-virtual',\n `${this.base}${node}${couponContext}${node}${virtualNode}(/${this.urlConfig.translations.betBuilder}|${node})$`,\n this.getCouponVirtualCompetitionRoute,\n );\n register('worldCupHub', `${this.base}${node}${worldCupHubContext}`, this.getWorldCupHubRoute);\n\n routerEvents.currentRoutesRecognized.subscribe((event) => {\n if (event) {\n const url = event.urlAfterRedirects || event.url || this.router.url;\n const parsed = this.urlService.parse(url);\n\n if (!this.parsedUrl || !isEqual(this.parsedUrl.path(), parsed.path()) || !isEqual(this.parsedUrl.search, parsed.search)) {\n this.parsedUrl = parsed;\n this.parsedRoute = this.parseUrl(parsed);\n }\n }\n });\n }\n\n current(): string {\n // as from vanilla code, they depend on window.location.pathname which is always encoded, so we need to decode\n return decodeURI(this.parsedUrl.url());\n }\n\n path(): string {\n // as from vanilla code, they depend on window.location.pathname which is always encoded, so we need to decode\n return decodeURI(this.parsedUrl.path());\n }\n\n params(): CompetitionRoute {\n return cloneDeep(this.parsedRoute);\n }\n\n parse(url: string): CompetitionRoute {\n const parsed = this.urlService.parse(url);\n\n return this.parseUrl(parsed);\n }\n\n private parseUrl(url: ParsedUrl): CompetitionRoute {\n let result: CompetitionRoute = {};\n\n // Root-level secondary outlets take the form e.g. /en/sports(overlay:menu/subpath)\n const decodedPathWithSecondaryOutletsRemoved = decodeURI(url.path()).replace(/\\(.*\\)/giu, '');\n\n for (const type of this.matchers.keys()) {\n const matcher = this.matchers.get(type);\n const mapper = this.mappers.get(type);\n\n if (matcher && mapper) {\n const match = matcher.exec(decodedPathWithSecondaryOutletsRemoved);\n\n if (match) {\n result = mapper(match);\n\n break;\n }\n }\n }\n\n return result;\n }\n\n private getQuery = (key: string) => this.parsedUrl.search.get(key) || undefined;\n private getInteger = (value?: string) => toInteger(value) || undefined;\n private getIntegerArray = (value?: string) => {\n const values = (value || '')\n .split(',')\n .map(this.getInteger)\n .filter((current) => !!current) as number[];\n\n if (values.length === 0) {\n return;\n }\n\n if (values.length === 1) {\n return values.pop();\n }\n\n return values;\n };\n\n private getSportRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n subContext: result[9],\n });\n\n private getBettingRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[15]),\n leagueName: result[14],\n });\n\n private getConferencesRoute: RegExpMapper = (result) => ({\n ...this.getBettingRoute(result),\n conference: result[19] ? this.getInteger(result[19]) || 0 : undefined,\n conferenceName: result[18],\n });\n\n private getVirtualBettingRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[16]),\n leagueName: result[14],\n virtualCompetitionGroup: this.getInteger(result[20]),\n virtualCompetitionGroupName: result[19],\n isVirtual: true,\n });\n\n private getBettingSportRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n league: this.getIntegerArray(result[8]),\n });\n\n private getCouponRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n coupon: this.getInteger(result[12]),\n couponName: result[11],\n });\n\n private getTournamentRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n tournament: result[9],\n league: this.getIntegerArray(result[13]),\n leagueName: result[12],\n });\n\n private getStandingsRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n context: this.urlConfig.translations.standings,\n leagueName: result[12],\n league: toInteger(result[13]),\n region: toInteger(result[9]),\n regionName: result[8],\n });\n\n private getStandingsVirtualRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n context: this.urlConfig.translations.standings,\n virtualCompetitionGroup: result[18] !== undefined ? toInteger(result[18]) : undefined,\n virtualCompetitionGroupName: result[17],\n isVirtual: true,\n leagueName: result[12],\n league: toInteger(result[14]),\n region: toInteger(result[9]),\n regionName: result[8],\n });\n\n private getCalendarRoute: RegExpMapper = (result) => ({\n base: result[1],\n context: result[3],\n });\n\n private getWorldCupHubRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n context: this.urlConfig.translations.worldCupHub,\n });\n\n private getBetsRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n league: this.getInteger(result[13]),\n leagueName: result[12],\n region: this.getInteger(result[9]),\n regionName: result[8],\n conference: this.getInteger(result[17]),\n conferenceName: result[16],\n marketOffer: this.getQuery('category'),\n outrightCategory: this.getQuery('outrightCategory'),\n specialCategory: this.getQuery('specialCategory'),\n context: this.urlConfig.translations.bets,\n });\n\n private getBetsVirtualRoute: RegExpMapper = (result) => ({\n ...this.getCommon(result),\n marketOffer: this.getQuery('category'),\n outrightCategory: this.getQuery('outrightCategory'),\n specialCategory: this.getQuery('specialCategory'),\n context: this.urlConfig.translations.bets,\n isVirtual: true,\n leagueName: result[12],\n league: toInteger(result[14]),\n region: toInteger(result[9]),\n regionName: result[8],\n });\n\n private getCommon: RegExpMapper = (result) => ({\n base: result[1],\n sport: this.getInteger(result[5]),\n sportName: result[4],\n context: result[7],\n marketTemplate: this.getInteger(this.getQuery('marketTemplate')),\n marketCategory: this.getInteger(this.getQuery('marketCategory')),\n marketOffer: this.getQuery('tab'),\n dynamicOfferCategory: this.getQuery('dynamicCategory'),\n });\n\n private getLiveRoute: RegExpMapper = (result) => ({\n base: result[1],\n sport: this.getInteger(result[7]),\n sportName: result[6],\n context: result[3],\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[15]),\n leagueName: result[14],\n marketTemplate: this.getInteger(this.getQuery('marketTemplate')),\n marketCategory: this.getInteger(this.getQuery('marketCategory')),\n marketOffer: this.getQuery('tab'),\n });\n\n private getLiveConferenceRoute: RegExpMapper = (result) => ({\n ...this.getLiveRoute(result),\n conference: this.getInteger(result[19]),\n conferenceName: result[18],\n });\n\n private getVirtualLiveRoute: RegExpMapper = (result) => ({\n ...this.getLiveRoute(result),\n league: this.getIntegerArray(result[16]),\n virtualCompetitionGroup: this.getInteger(result[20]),\n virtualCompetitionGroupName: result[19],\n isVirtual: true,\n });\n\n private getCouponCompetitionRoute: RegExpMapper = (result) => ({\n ...this.getBettingRoute(result),\n coupon: this.getInteger(result[20]),\n couponName: result[19],\n });\n\n private getCouponVirtualCompetitionRoute: RegExpMapper = (result) => ({\n base: result[1],\n sport: this.getInteger(result[5]),\n sportName: result[4],\n context: result[7],\n region: this.getInteger(result[11]),\n regionName: result[10],\n league: this.getIntegerArray(result[16]),\n leagueName: result[14],\n isVirtual: true,\n coupon: this.getInteger(result[21]),\n couponName: result[20],\n });\n\n getCompetitionRouteTracking(hasFixtures: boolean): string {\n const params = this.params() ?? {};\n if (params.isVirtual) {\n let groupName = params.virtualCompetitionGroupName;\n\n if (!groupName) {\n groupName = 'All';\n }\n\n return `EventList/League/${groupName}`;\n }\n\n if (hasFixtures && params.league) {\n return 'EventList/League';\n }\n\n const routeContext = params.context;\n\n return `EventList/${capitalize(routeContext || 'all')}`;\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { VirtualCompetitionGroupItem } from '@frontend/sports/common/core/data-access/sport-model';\n\nimport { FixtureList } from '../event-list-shared/sport/competitions/competition.models';\n\n@Injectable({ providedIn: 'root' })\nexport class CompetitionListSeoService {\n private realCompetitionId: number | undefined;\n\n getRealCompetitionId(): number | undefined {\n return this.realCompetitionId;\n }\n\n saveRealCompetitionId(id: number | undefined): void {\n this.realCompetitionId = id;\n }\n\n storeRealCompetitionId(fixtureList: FixtureList | undefined): void {\n this.realCompetitionId = undefined;\n const virtualCompetition = fixtureList && fixtureList.virtualCompetition;\n if (virtualCompetition) {\n const virtualGroupId = fixtureList.params && fixtureList.params.virtualCompetitionGroup;\n const virtualGroup =\n virtualCompetition.children &&\n (virtualCompetition.children.find((group) => group.id === virtualGroupId) as VirtualCompetitionGroupItem | undefined);\n this.realCompetitionId = virtualGroup ? virtualGroup.siblings[0] : virtualCompetition.siblings[0];\n }\n }\n}\n","import { Injectable } from '@angular/core';\nimport { Params } from '@angular/router';\n\nimport { ApiService } from '@frontend/sports/common/api-utils';\nimport { PrettyUrlsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { assign, isArray, isEqual, keys, pickBy } from 'lodash-es';\nimport { Observable, of } from 'rxjs';\nimport { catchError, map, switchMap, tap } from 'rxjs/operators';\n\nimport { MasterdataApiService } from '../cds/cds-masterdata-api.service';\nimport { CompetitionListSeoService } from '../competition-list/competition-list-seo.service';\nimport { CompetitionRouteService } from '../competition-list/competition-route.service';\nimport { SportUrlParam } from '../navigation-core/url-helper.service';\nimport { CompetitionRoute } from '../navigation/navigation.models';\nimport { SeoContentApiRequest, SeoContentApiResponse, SeoRoute } from './seo.models';\n\ninterface SeoContentRequest {\n filter?: string;\n lobby?: boolean;\n live?: boolean;\n virtual?: boolean;\n highlights?: boolean;\n allSports?: boolean;\n sport?: number;\n region?: number | string;\n league?: number;\n event?: string;\n video?: boolean;\n conference?: number;\n virtualCompetitionId?: number;\n virtualCompetitionGroupId?: number;\n esportsLobby?: boolean;\n esportsHighlights?: boolean;\n multiSportsLobby?: boolean;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SeoContentService {\n private lastRequestApiParams: SeoContentApiRequest | null = null;\n private lastResponse: SeoContentApiResponse | null = null;\n private readonly logger: SportsRemoteLogger;\n\n constructor(\n private competitionRoute: CompetitionRouteService,\n private api: ApiService,\n loggerFactory: LoggerFactory,\n private urlConfig: PrettyUrlsConfig,\n private competitionListSeoService: CompetitionListSeoService,\n private masterDataApiService: MasterdataApiService,\n ) {\n this.logger = loggerFactory.getLogger('SeoContentService');\n }\n\n getForEventDetails(request: SeoContentRequest): Observable {\n return this.performApiRequest(request);\n }\n\n getSeoContent(routeParams: Params, data: SeoRoute): Observable {\n const competitionData = this.competitionRoute.params();\n const league = competitionData.league;\n const routeData = assign(competitionData, routeParams);\n\n if (!routeData) {\n return of(undefined);\n }\n\n return this.getLeagueId(routeData, league || routeData.league).pipe(\n switchMap((leagueId) => {\n return this.performApiRequest({\n filter: this.getSeoFilter(routeData),\n lobby: data.sportsLobby,\n live: data.live,\n virtual: data.virtual,\n highlights: data.liveHighlights,\n allSports: data.sportsList,\n sport: routeData.sport,\n region: routeData.region || routeData.tournament,\n league: leagueId,\n video: data.liveVideo,\n conference: routeData.conference,\n virtualCompetitionId: routeData.isVirtual ? routeParams.league : null,\n virtualCompetitionGroupId: routeData.isVirtual ? routeParams.virtualCompetitionGroup : null,\n esportsLobby: data.esportsLobby,\n esportsHighlights: data.esportsHighlights,\n multiSportsLobby: data.multiSportsLobby,\n }).pipe(\n tap((response) => {\n if (response) {\n const fixtureContext: (string | undefined)[] = [\n this.urlConfig.translations.betting,\n this.urlConfig.translations.conferences,\n this.urlConfig.translations.after2days,\n this.urlConfig.translations.after3days,\n this.urlConfig.translations.in30minutes,\n this.urlConfig.translations.in60minutes,\n this.urlConfig.translations.in180minutes,\n this.urlConfig.translations.today,\n this.urlConfig.translations.tomorrow,\n ];\n\n response.h1Included = fixtureContext.includes(competitionData.context) && !!competitionData.region;\n }\n }),\n );\n }),\n );\n }\n\n private getLeagueId(routeData: CompetitionRoute & Params, leagueId: number | number[] | undefined): Observable {\n const league = isArray(leagueId) ? undefined : leagueId;\n if (!routeData.isVirtual) {\n return of(league);\n }\n const realCompetitionId = this.competitionListSeoService.getRealCompetitionId();\n if (realCompetitionId) {\n return of(realCompetitionId);\n }\n if (routeData.sport && league) {\n return this.masterDataApiService\n .getVirtualCompetitionInfo({\n competitionId: league,\n sportId: routeData.sport,\n virtualCompetitionGroupId: routeData.virtualCompetitionGroup || 0,\n virtualCompetitionId: league,\n })\n .pipe(\n map(\n (virtualCompetitionInfo) =>\n virtualCompetitionInfo?.virtualCompetitionGroup?.competitionIds[0] ||\n virtualCompetitionInfo?.virtualCompetition?.competitionIds[0],\n ),\n catchError((err) => {\n if (err.status) {\n this.logger.error(err, 'Error fetching virtual competition info');\n }\n\n return of(undefined);\n }),\n );\n }\n\n return of(league);\n }\n\n private performApiRequest(request: SeoContentRequest): Observable {\n const apiParams: SeoContentApiRequest = pickBy(request, (prop) => prop != null && prop !== false && prop !== '');\n\n if (this.lastResponse != null && isEqual(apiParams, this.lastRequestApiParams)) {\n return of(this.lastResponse);\n }\n\n return this.api.get('seo/tags', apiParams).pipe(\n tap((response) => {\n this.lastRequestApiParams = apiParams;\n this.lastResponse = response;\n }),\n catchError((err) => {\n // check if request got cancelled (happens e.g. if the user quickly navigates to another page or if the signal was lost) and only log exception if it's a real error\n if (err.status) {\n this.logger.error(err, 'Error getting SEO tags');\n }\n\n return of(undefined);\n }),\n );\n }\n\n private getSeoFilter(routeData: CompetitionRoute & Params): string | undefined {\n const contextKey = (input: string | undefined) =>\n keys(this.urlConfig.translations)\n .filter((prop) => this.urlConfig.translations[prop] === input)\n .pop();\n\n const subContext = contextKey(routeData.subContext);\n\n return subContext === SportUrlParam.Competitions ? subContext : contextKey(routeData.context);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { trackingConstants } from '@frontend/sports/tracking/feature';\nimport { isNil } from 'lodash-es';\nimport { IPickTracking } from 'packages/sports/common/betslip/core/picks/pick-models';\n\n@Injectable({ providedIn: 'root' })\nexport class PickSourceProvider {\n getTracking(): { [key: string]: string } {\n return {};\n }\n\n get(\n source: string,\n contentPosition?: number,\n isFallbackMarketEnabled?: boolean,\n marqueeName?: string,\n sitecoreTemplateId?: string,\n marqueeType?: string,\n trackingOptions?: { [key: string]: string },\n isAutomatedMarquee?: boolean,\n isInSheetView?: boolean,\n ): IPickTracking {\n const baseTracking: IPickTracking = {\n source,\n additional: trackingOptions || {},\n };\n if (!isNil(contentPosition)) {\n baseTracking[trackingConstants.COMPONENT_CONTENT_POSITION] = contentPosition.toString();\n }\n\n if (!isNil(isAutomatedMarquee)) {\n baseTracking.additional![trackingConstants.MARQUEE_CONTENT_LOGIC] = isAutomatedMarquee ? 'default - automated' : 'default';\n }\n\n if (isInSheetView) {\n baseTracking.additional![trackingConstants.COMPONENT_MODULE_NAME] = trackingConstants.SEE_ALL_OVERLAY;\n baseTracking.sheetviewSuffix = `/${trackingConstants.SEE_ALL_OVERLAY}`;\n }\n\n return baseTracking;\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Widget, WidgetLayoutTemplate, WidgetPage } from '@frontend/sports/types/components/widget';\n\n@Injectable()\nexport class ModularConfigAccessorService {\n private widget?: Widget;\n private parent?: Widget;\n private page?: WidgetPage;\n private layoutTemplate?: WidgetLayoutTemplate;\n\n setPage(page: WidgetPage | undefined): void {\n this.page = page;\n }\n\n getPage(): WidgetPage | undefined {\n return this.page;\n }\n\n setWidget(widget: Widget): void {\n this.widget = widget;\n }\n\n getWidget(): Readonly> | undefined {\n return this.widget;\n }\n\n setParentWidget(widget: Widget | undefined): void {\n this.parent = widget;\n }\n\n getParentWidget(): Readonly> | undefined {\n return this.parent;\n }\n\n setLayoutTemplate(layoutTemplate: WidgetLayoutTemplate | undefined): void {\n this.layoutTemplate = layoutTemplate;\n }\n\n getLayoutTemplate(): WidgetLayoutTemplate | undefined {\n return this.layoutTemplate;\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { hasValue } from '@frontend/sports/common/core/utils/extended-types';\nimport { trackingConstants } from '@frontend/sports/tracking/feature';\nimport { Widget } from '@frontend/sports/types/components/widget';\nimport { isEmpty, isNil } from 'lodash-es';\nimport { IPickTracking } from 'packages/sports/common/betslip/core/picks/pick-models';\n\nimport { PickSourceProvider } from '../../option-pick/pick-source.provider';\nimport { ModularConfigAccessorService } from './modular-config-accessor.service';\n\n@Injectable()\nexport class ModularPickSourceService extends PickSourceProvider {\n constructor(private configAccessor: ModularConfigAccessorService) {\n super();\n }\n\n override getTracking(): { [key: string]: string } {\n return this.getBaseTracking();\n }\n\n override get(\n source: string,\n contentPosition?: number,\n isFallbackMarketEnabled?: boolean,\n marqueeName?: string,\n sitecoreTemplateId?: string,\n marqueeType?: string,\n trackingOptions?: { [key: string]: string },\n isAutomatedMarquee?: boolean,\n ): IPickTracking {\n const config = this.configAccessor.getWidget();\n const page = this.configAccessor.getPage();\n\n if (!config) {\n return super.get(source);\n }\n\n const baseTracking = {\n source: this.buildPath(page, config.templateName, source).toLowerCase(),\n additional: this.getBaseTracking(config),\n };\n\n if (!isNil(contentPosition)) {\n baseTracking.additional[trackingConstants.COMPONENT_CONTENT_POSITION] = contentPosition.toString();\n }\n\n if (!isNil(isAutomatedMarquee)) {\n baseTracking.additional[trackingConstants.MARQUEE_CONTENT_LOGIC] = isAutomatedMarquee ? 'default - automated' : 'default';\n } else if (!isNil(isFallbackMarketEnabled)) {\n baseTracking.additional[trackingConstants.MARQUEE_CONTENT_LOGIC] = isFallbackMarketEnabled ? 'fallback' : 'default';\n }\n\n if (!isNil(marqueeName)) {\n baseTracking.additional[trackingConstants.MARQUEE_NAME] = marqueeName;\n }\n\n if (!isNil(sitecoreTemplateId)) {\n baseTracking.additional[trackingConstants.SITECORE_TEMPLATE_ID] = sitecoreTemplateId;\n }\n\n if (!isNil(marqueeType)) {\n baseTracking.additional[trackingConstants.MARQUEE_TYPE] = marqueeType;\n }\n\n return baseTracking;\n }\n\n private getBaseTracking(config?: Readonly>): { [key: string]: string } {\n config = config || this.configAccessor.getWidget();\n const parentWidget = this.configAccessor.getParentWidget();\n\n let baseTracking = {};\n\n if (config) {\n baseTracking = Object.assign(baseTracking, config.trackingData, {\n [trackingConstants.COMPONENT_MODULE_NAME]: config.type,\n [trackingConstants.COMPONENT_MODULE_POSITION]: `${config.location}|${config.order}`,\n [trackingConstants.COMPONENT_MODULE_CUSTOM_NAME]: config.templateName,\n [trackingConstants.COMPONENT_MODULE_SOURCE]: isNil(parentWidget) ? 'standard module' : `composable|${parentWidget.templateName}`,\n });\n }\n\n const layoutTemplate = this.configAccessor.getLayoutTemplate();\n const page = this.configAccessor.getPage();\n if (layoutTemplate) {\n baseTracking = Object.assign(baseTracking, {\n [trackingConstants.COMPONENT_PAGE_LAYOUT]: this.buildPath(layoutTemplate.folder, page, layoutTemplate.name),\n });\n }\n\n return !isEmpty(baseTracking) ? baseTracking : super.getTracking();\n }\n\n private buildPath(...params: unknown[]): string {\n return params.filter(hasValue).join('/');\n }\n}\n","import { Nullable } from '@frontend/sports/common/core/utils/extended-types';\nimport { Odds } from '@frontend/sports/odds/feature';\nimport { Decimal } from 'decimal.js';\nimport { NumpadAction } from 'packages/sports/web/app/src/numpad/model';\n\nimport { PickId } from '../../core/picks/pick-id';\nimport { SlipResult } from '../betplacement/models';\nimport { BetPlacementError } from '../validation/errors/bet-placement-error';\nimport { BetslipError } from '../validation/errors/betslip-error';\n\nexport enum QuickBetActionButtonState {\n Login = 'Login',\n MakeDeposit = 'MakeDeposit',\n Place = 'Place',\n PlaceFreeBet = 'PlaceFreeBet',\n Placing = 'Placing',\n PlacingFreeBet = 'PlacingFreeBet',\n ProcessingDeposit = 'ProcessingDeposit',\n ProcessingDepositAndBet = 'ProcessingDepositAndBet',\n AcceptChanges = 'AcceptChanges',\n AcceptAndPlace = 'AcceptAndPlace',\n AcceptAndPlaceFreeBet = 'AcceptAndPlaceFreeBet',\n Deposit = 'Deposit',\n DepositAndPlaceBet = 'DepositAndPlaceBet',\n UpdateMinStake = 'UpdateMinStake',\n}\n\nexport enum QuickBetUiState {\n None = 'None',\n Loading = 'Loading',\n Place = 'Place',\n Success = 'Success',\n}\n\nexport interface IQuickBetActionButtonState {\n isDisabled: boolean;\n isProcessing: boolean;\n state: QuickBetActionButtonState;\n hasTaxation: boolean;\n possibleWinnings: Nullable;\n winningsBoost: Nullable;\n riskFreeAmount: Nullable;\n taxationAmount: Nullable;\n taxationLiability: Nullable;\n possibleWinningsNet: Nullable;\n}\n\nexport interface IQuickBetErrorsStatus {\n isLocked: boolean;\n isClosed: boolean;\n oddsChanged: boolean;\n oddsAccepted: boolean;\n hasBetslipErrors: boolean;\n hasStakeUpdateErrors: boolean;\n}\n\nexport interface IQuickBetWinningsState {\n hasTaxation: boolean;\n possibleWinnings: Nullable;\n winningsBoost: Nullable;\n riskFreeAmount: Nullable;\n taxationAmount: Nullable;\n taxationLiability: Nullable;\n taxationRate: Nullable;\n possibleWinningsNet: Nullable;\n originalWinnings?: Nullable;\n}\n\nexport const defaultQuickBetActionButtonState: IQuickBetActionButtonState = {\n isDisabled: false,\n isProcessing: false,\n state: QuickBetActionButtonState.Login,\n hasTaxation: false,\n possibleWinnings: null,\n winningsBoost: null,\n riskFreeAmount: null,\n taxationAmount: null,\n taxationLiability: null,\n possibleWinningsNet: null,\n};\n\nexport interface IQuickBetStakeState {\n stake: Nullable; // numpad-keys.component work with strings\n numpadAction?: NumpadAction;\n}\n\nexport const defaultQuickBetStakeState: IQuickBetStakeState = {\n stake: null,\n};\n\nexport interface IQuickBetPlaceResultWarning {\n disableButton: boolean;\n error: BetPlacementError;\n}\n\nexport interface IQuickBetPlaceResultSummaryState {\n stake: number;\n numberOfBets: number;\n currency?: string;\n possibleWinningsGross: number;\n possibleWinningsNet: number;\n}\n\nexport interface IQuickBetPlaceResultState {\n betNumber: string;\n slipResults: SlipResult;\n isSuccessful: boolean;\n errors: BetslipError[];\n warnings: IQuickBetPlaceResultWarning[];\n placedNewCustomerOffer: boolean;\n summary: IQuickBetPlaceResultSummaryState;\n isEachWay: boolean;\n acceptedOdds: Odds | null;\n boostedOdds: Odds | null;\n}\n\nexport const defaultQuickBetPlaceResultState: IQuickBetPlaceResultState = {\n betNumber: '',\n slipResults: {},\n isSuccessful: false,\n errors: [],\n warnings: [],\n placedNewCustomerOffer: false,\n summary: {},\n isEachWay: false,\n acceptedOdds: null,\n boostedOdds: null,\n};\n\nexport interface IQuickBetHelpState {\n isVisible: boolean;\n wasShown: boolean;\n}\n\nexport const defaultQuickBetHelpState: IQuickBetHelpState = {\n isVisible: false,\n wasShown: false,\n};\n\nexport interface IQuickBetState {\n uiState: QuickBetUiState;\n isDisabled: boolean;\n keepOpen: boolean;\n pickIds: PickId[];\n oddsChangedOnBetPlacement: boolean;\n pickLockedOnBetPlacement: boolean;\n displayHelper: QuickBetDisplayHelper;\n hasClosedPick: boolean;\n hasLockedPick: boolean;\n helpState: IQuickBetHelpState;\n stakeState: IQuickBetStakeState;\n actionButtonState: IQuickBetActionButtonState;\n betPlacedResult: IQuickBetPlaceResultState;\n inOverlay: boolean;\n inEditBetMode: boolean;\n}\n\nexport interface QuickBetDisplayHelper {\n addedToBetslip: boolean;\n //hidden => QB can be reopend if the user correct the error from betbar\n hiddenByError: boolean;\n //the user did not take care of the error we are closing QB\n closedByError: boolean;\n}\n\nexport const defaultQuickBetDisplayHelper: QuickBetDisplayHelper = {\n addedToBetslip: false,\n closedByError: false,\n hiddenByError: false,\n};\n\nexport const defaultQuickBetState: IQuickBetState = {\n uiState: QuickBetUiState.None,\n isDisabled: false,\n keepOpen: false,\n pickIds: [],\n displayHelper: defaultQuickBetDisplayHelper,\n oddsChangedOnBetPlacement: false,\n pickLockedOnBetPlacement: false,\n hasClosedPick: false,\n hasLockedPick: false,\n helpState: defaultQuickBetHelpState,\n stakeState: defaultQuickBetStakeState,\n actionButtonState: defaultQuickBetActionButtonState,\n betPlacedResult: defaultQuickBetPlaceResultState,\n inOverlay: false,\n inEditBetMode: false,\n};\n\nexport interface QuickBetStore {\n helpShown: boolean;\n}\n\nexport interface IQuickBetPlaceResultStorageState {\n betNumber: string;\n isSuccessful: boolean;\n errors: BetslipError[];\n warnings: IQuickBetPlaceResultWarning[];\n placedNewCustomerOffer: boolean;\n summary: IQuickBetPlaceResultSummaryState;\n isEachWay: boolean;\n acceptedOdds: Odds | null;\n boostedOdds: Odds | null;\n}\n\nexport interface IQuickBetSaveState {\n pickIds: string[];\n helpState: IQuickBetHelpState;\n keepOpen: boolean;\n stakeState: IQuickBetStakeState;\n actionButtonState: IQuickBetActionButtonState;\n betPlacedResult: IQuickBetPlaceResultStorageState;\n}\n","import { createSelector } from '@ngrx/store';\nimport { pickBy } from 'lodash-es';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { PickId } from '../../core/picks/pick-id';\nimport { isBetBuilderPick } from '../../core/utils';\nimport { betslipPicksListSelector, selectBetslipFlattenedPicksList, selectHasEachWayPick } from '../picks/selectors';\nimport { filterTypePicks } from '../picks/services/linear-betslip-pick.utils';\nimport { RewardTokenContext } from '../reward-tokens/reward-tokens.model';\nimport { selectIsRewardTokensSelectorVisible, selectRewardTokens } from '../reward-tokens/selectors';\nimport { betslipTypeStateSelector, selectBetBuilderPicksCount } from '../types/selectors';\nimport {\n selectBetslipTypeErrorsFactory,\n selectComboContainerModuleErrors,\n selectHasComboPreventionForTypeFactory,\n selectSlipErrorsForTypeFactory,\n} from '../validation/selectors';\n\nexport const comboBetStateSelector = createSelector(betslipTypeStateSelector, (typeState) => typeState.comboBet);\n\nexport const selectComboBetIsEachWay = createSelector(comboBetStateSelector, (comboBet) => comboBet.isEachWay);\n\nexport const comboBetStateStakeSelector = createSelector(comboBetStateSelector, (comboBet) => ({\n actualStake: comboBet.actualStake,\n stake: comboBet.stake,\n}));\n\nexport const comboBetPicksSelector = createSelector(comboBetStateSelector, (s) => s.picks);\n\nexport const comboBetPickSelectorFactory = (pickId: PickId) =>\n createSelector(comboBetPicksSelector, (picks) => {\n return picks[pickId.toString()];\n });\n\nexport const selectComboBetActualStake = createSelector(comboBetStateSelector, (state) => state.actualStake);\nexport const selectComboBetStake = createSelector(comboBetStateSelector, (state) => state.stake);\n\nexport const comboBetSelectedPicksSelector = createSelector(comboBetPicksSelector, (picks) => pickBy(picks, (pick) => pick.isSelected));\n\nexport const selectComboBetRewardTokenId = createSelector(comboBetStateSelector, (state) => state.rewardTokenId);\n\nexport const selectComboBetRewardToken = createSelector(selectComboBetRewardTokenId, selectRewardTokens, (tokenId, tokens) => {\n return tokenId ? tokens[tokenId] : null;\n});\n\nexport const selectComboBetPicks = createSelector(comboBetPicksSelector, selectBetslipFlattenedPicksList, (comboBetPicks, picksList) => {\n return picksList.filter((pick) => comboBetPicks[pick.id.toString()]);\n});\n\nexport const selectComboBetEachWay = createSelector(comboBetStateSelector, (comboState) => comboState.isEachWay);\n\nexport const selectComboBetComponentState = createSelector(comboBetStateSelector, selectComboBetPicks, (comboBetState, pickList) => ({\n comboBetState,\n pickList,\n}));\n\nexport const selectHasBetBuilderModuleErrors = createSelector(\n selectSlipErrorsForTypeFactory(BetslipType.BetBuilder),\n selectBetslipTypeErrorsFactory(BetslipType.BetBuilder),\n selectHasComboPreventionForTypeFactory(BetslipType.BetBuilder),\n (slipErrors, typeErrors, hasComboPrevention) => slipErrors.length > 0 || typeErrors.length > 0 || hasComboPrevention,\n);\n\nexport const selectPlaceableComboBetPicks = createSelector(comboBetPicksSelector, betslipPicksListSelector, (comboPicks, picksList) =>\n filterTypePicks(comboPicks, picksList, { isSelected: true, isLocked: false }),\n);\n\nexport const selectPlaceableComboBetBuilderPicksCount = createSelector(\n selectPlaceableComboBetPicks,\n (placeablePicks) => placeablePicks.filter(isBetBuilderPick).length,\n);\n\nexport const selectMainComboContainerState = (tokenContext: RewardTokenContext) =>\n createSelector(\n comboBetPicksSelector,\n selectComboBetEachWay,\n selectBetBuilderPicksCount,\n selectPlaceableComboBetPicks,\n selectPlaceableComboBetBuilderPicksCount,\n selectHasEachWayPick,\n selectIsRewardTokensSelectorVisible(tokenContext),\n selectComboContainerModuleErrors,\n selectHasComboPreventionForTypeFactory(BetslipType.Combo),\n (\n comboPicks,\n comboEachWay,\n betBuilderPicksCount,\n placeablePicks,\n placeableBetBuilderPicksCount,\n hasEachWayPick,\n isRewardSelectorVisible,\n moduleErrors,\n hasComboPrevention,\n ) => ({\n comboPicks,\n comboEachWay,\n betBuilderPicksCount,\n placeablePicks,\n placeableBetBuilderPicksCount,\n hasEachWayPick,\n isRewardSelectorVisible,\n moduleErrors,\n hasComboPrevention,\n }),\n );\n","import { Type } from '@angular/core';\n\nimport { BetslipError } from './betslip-error';\n\nexport interface INotifyUserError {\n userHasBeenNotified: boolean;\n\n markAsSeen(): void;\n}\n\nexport declare class NotifyUserError extends BetslipError implements INotifyUserError {\n userHasBeenNotified: boolean;\n markAsSeen(): void;\n}\n\nexport type ErrorConstructor = new (...args: any[]) => T;\n\nexport function NotifyUserErrorMixin<\n TBaseError extends BetslipError,\n TBaseErrorType extends ErrorConstructor = ErrorConstructor,\n>(\n // eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match\n BaseErrorType: TBaseErrorType,\n): Type {\n // @ts-ignore Constructor type https://github.com/Microsoft/TypeScript/issues/16390\n return class extends BaseErrorType implements INotifyUserError {\n constructor(...args: any[]) {\n super(...args);\n this.userHasBeenNotified = false;\n }\n\n userHasBeenNotified: boolean;\n\n markAsSeen(): void {\n this.userHasBeenNotified = true;\n }\n };\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\n\nimport { BetPlacementErrorIcon } from '../bet-placement-error-icon';\nimport { BetslipError } from '../betslip-error';\nimport { NotifyUserErrorMixin } from '../notify-user-error';\nimport { ResultError } from './result-error';\n\nexport class PickInvisible extends NotifyUserErrorMixin(ResultError) {\n constructor(pickId: string) {\n super(pickId);\n this.icon = BetPlacementErrorIcon.Warning;\n this.priority = -999;\n this.hasClientValidation = true;\n this.type = PlacementErrorType.OptionInvisible;\n }\n\n override equals(error: BetslipError): boolean {\n return error instanceof PickInvisible && error.pickId === this.pickId;\n }\n}\n","import { PickInvisible } from '../result/pick-invisible';\nimport { IPickPreCheckError, PreCheckErrorMixin } from './pre-check-error';\n\n/**\n * Raised when market/option/price visibility is false.\n */\nexport class PickLockedPreCheckError extends PreCheckErrorMixin(PickInvisible) implements IPickPreCheckError {\n constructor(pickId: string) {\n super(pickId);\n }\n}\n","import { PlacementErrorType } from '@frontend/sports/types/betslip';\nimport { createSelector } from '@ngrx/store';\n\nimport { BetslipType } from '../../core/betslip-type';\nimport { BetslipUnknownPick } from '../../core/picks/betslip-unknown-pick';\nimport { getLegsCount } from '../../core/utils';\nimport { comboBetPicksSelector, comboBetSelectedPicksSelector } from '../combo-bet/selectors';\nimport { betslipPicksListSelector, selectBetslipFlattenedPicksList } from '../picks/selectors';\nimport { singleBetSelectedPicksSelector } from '../single-bet/selectors';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { MinSelectionsBetbuilderError } from '../validation/errors/general/min-selections-betbuilder-error';\nimport { PickLockedPreCheckError } from '../validation/errors/pre-check/pick-locked-pre-check-error';\nimport { selectBetslipErrors, selectCurrentPicksErrors, selectPickErrorsState } from '../validation/selectors';\nimport { isPickStatusChangeError } from '../validation/services/utils/betslip-errors-utils';\n\nexport const selectedComboPicksSelector = createSelector(betslipPicksListSelector, comboBetSelectedPicksSelector, (pickList, selectedPicks) =>\n pickList.filter(({ id }) => !!selectedPicks[id.toString()]),\n);\n\n//include unselected picks\nexport const comboPickListCountSelector = createSelector(betslipPicksListSelector, comboBetPicksSelector, (pickList, comboBetPicks) => {\n const comboBetPick = pickList.filter(({ id }) => !!comboBetPicks[id.toString()]);\n\n return getLegsCount(comboBetPick);\n});\n\nexport const selectSelectedSinglePicks = createSelector(selectBetslipFlattenedPicksList, singleBetSelectedPicksSelector, (pickList, selectedPicks) =>\n pickList.filter(({ id }) => !!selectedPicks[id.toString()]),\n);\n\nexport const hasPickLockedPreCheckErrorSelector = createSelector(selectedComboPicksSelector, selectPickErrorsState, (selectedPicks, pickErrors) =>\n selectedPicks.some((pick) => {\n const errors = Object.values(pickErrors).flatMap((e) => e[pick.id.toString()] ?? []);\n\n return errors.some((e) => e instanceof PickLockedPreCheckError);\n }),\n);\n\nexport const hasComboPreventionErrorSelector = createSelector(selectedComboPicksSelector, selectCurrentPicksErrors, (selectedPicks, pickErrors) =>\n selectedPicks.some((pick) => {\n const errors = pickErrors[pick.id.toString()] ?? [];\n\n return errors.some((e) => e.type === PlacementErrorType.ComboPrevention);\n }),\n);\n\nexport const pickStatusChangeErrorSelector = createSelector(selectedComboPicksSelector, selectPickErrorsState, (selectedPicks, pickErrors) =>\n selectedPicks.some((pick) => {\n const errors = Object.values(pickErrors).flatMap((e) => e[pick.id.toString()] ?? []);\n\n return errors.some((e) => isPickStatusChangeError(e));\n }),\n);\n\nexport const singlePickCountSelector = createSelector(selectSelectedSinglePicks, (selectedPicks) => {\n return getLegsCount(selectedPicks);\n});\n\nexport const comboPickCountSelector = createSelector(selectedComboPicksSelector, (selectedPicks) => {\n return getLegsCount(selectedPicks);\n});\n\nexport const hasMinSelectionsBetbuilderError = createSelector(selectBetslipErrors, (betslipError) =>\n betslipError.some((e) => e instanceof MinSelectionsBetbuilderError),\n);\n\nexport const selectedPicksByCurrentTypeSelector = createSelector(\n betslipCurrentTypeSelector,\n selectedComboPicksSelector,\n selectSelectedSinglePicks,\n (currentType, selectedComboPicks, selectedSinglePicks) => {\n return currentType === BetslipType.Combo\n ? selectedComboPicks.filter((pick) => !BetslipUnknownPick.isPick(pick))\n : selectedSinglePicks.filter((pick) => !BetslipUnknownPick.isPick(pick));\n },\n);\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipSelector } from '../../base/store/selectors';\n\nexport const settingsSelector = createSelector(betslipSelector, (s) => s.settings);\nexport const settingsOddsAcceptanceSelector = createSelector(settingsSelector, (s) => s.oddsAcceptance);\nexport const settingsNotificationsSelector = createSelector(settingsSelector, (s) => s.notifications);\n","import { createSelector } from '@ngrx/store';\n\nimport { getStakedTypes } from '../betplacement/services/stake/linear-stake-utils';\nimport { selectRewardTokens } from '../reward-tokens/selectors';\nimport { singleBetPicksGeneralStakeSelector } from '../single-bet/selectors';\nimport { GetSummaryStake } from '../stake/utils';\nimport { betslipTypeStateSelector, selectIsSingleBetSelected } from '../types/selectors';\nimport { selectCurrentStakeError } from '../validation/selectors';\n\nexport const selectSummaryStake = createSelector(betslipTypeStateSelector, (types) => GetSummaryStake(types));\n\nexport const selectCurrentStake = createSelector(\n selectSummaryStake,\n selectIsSingleBetSelected,\n singleBetPicksGeneralStakeSelector,\n (summaryStake, isSingleBet, singleBetStake) => (isSingleBet ? (singleBetStake?.actualStake ?? null) : summaryStake),\n);\n\nexport const selectStakeValidationState = createSelector(selectCurrentStake, selectCurrentStakeError, (stake, stakeError) => ({\n stake,\n stakeError,\n}));\n\nexport const selectStakedBetslipTypes = createSelector(betslipTypeStateSelector, selectRewardTokens, (types, tokens) =>\n getStakedTypes(types, tokens),\n);\n","export enum BetslipActionButtonTracking {\n NotApplicable = 'not applicable',\n Betslip = 'Betslip',\n QuickBet = 'QuickBet',\n EditMyBet = 'Edit My Bet',\n PlaceBet = 'Place bet',\n PlaceFreeBet = 'Place free bet',\n AcceptChanges = 'Accept changes',\n AcceptAndPlace = 'Accept&place bet',\n AcceptAndPlaceFreeBet = 'Accept&place freebet',\n LoginToBet = 'Login to bet',\n MakeDeposit = 'Make a deposit',\n Deposit = 'Deposit',\n DepositAndPlace = 'Deposit&place bet',\n Confirm = 'Confirm',\n SaveChanges = 'Save changes',\n}\n\nexport enum BetslipLinearTracking {\n NotApplicable = 'not applicable',\n Betslip = 'Betslip',\n FullBetslip = 'full betslip',\n Click = 'click',\n LinearCheckbox = 'linear checkbox',\n LinearNoCheckbox = 'linear no checkbox',\n Tabbed = 'tabbed',\n Combo = 'combo bet',\n Single = 'single bet',\n System = 'system bet',\n Teaser = 'teaser bet',\n EditBet = 'edit bet',\n Load = 'load',\n Icon = 'Icon',\n BetBuilder = 'betbuilder bet',\n SuccessMessage = 'success message',\n AddPromo = 'add promo',\n PromotionsPopup = 'rewards popup/active promotions',\n Close = 'close',\n SettingsIcon = 'settings icon',\n Settings = 'betslip settings',\n SettingsPopup = 'settings popup',\n}\n\nexport enum LinearCtaTrackingType {\n CloseCta = 'close cta',\n DepositCta = 'deposit cta',\n}\n\nexport enum PickBetTrackingTypes {\n Single = 'single',\n Parlay = 'parlay',\n Sgp = 'sgp',\n SgpPlus = 'sgp+',\n Teaser = 'teaser',\n System = 'system',\n}\n\nexport enum InfoTrackingContext {\n ComboBet = 'combo bet',\n Sgp = 'sgp',\n SgpPlus = 'sgp+',\n SystemBet = 'system bet',\n TeaserBet = 'teaser bet',\n}\n\nexport enum LinearCheckboxTrackingState {\n Selected = 'selected',\n Unselected = 'unselected',\n}\n\nexport enum LinearModuleExpansionTrackingState {\n Expand = 'expand',\n Collapse = 'collapse',\n}\n\nexport const BetslipTrackingLocation = 'Betslip';\nexport const LinearBetslipTrackingLocation = 'linear';\nexport const QuickBetTrackingLocation = 'Quick Bet';\n","import { createSelector } from '@ngrx/store';\n\nimport { betslipBaseSelector, betslipSelector, selectIsLinearBetslip } from '../../base/store/selectors';\nimport { BetslipType } from '../../core/betslip-type';\nimport { BetslipBetBuilderPick } from '../../core/picks/betslip-bet-builder-pick';\nimport { BetBuilderPickId, PickId } from '../../core/picks/pick-id';\nimport { getLegsCount, isBetBuilderPickId } from '../../core/utils';\nimport { comboPickCountSelector, singlePickCountSelector } from '../betslip-bar/selectors';\nimport { betslipPicksListSelector } from '../picks/selectors';\nimport { CriteriaType } from '../reward-tokens/reward-tokens.model';\nimport { selectTokensStateContext } from '../reward-tokens/selectors';\nimport { getSelectedTokenAndEligibilityForContext, getSelectedTokenForContext } from '../reward-tokens/services/linear-reward-tokens.utils';\nimport { isFreebetToken } from '../reward-tokens/services/reward-tokens.utils';\nimport { settingsOddsAcceptanceSelector } from '../settings/selectors';\nimport { selectCurrentStake } from '../summary/selectors';\nimport { PickBetTrackingTypes } from '../tracking/models';\nimport { betslipCurrentTypeSelector } from '../types/base/selectors';\nimport { betslipTypeStateSelector, selectBetBuilderPicksState } from '../types/selectors';\nimport { GroupWithClosedLegsError } from '../validation/errors/general/group-pick-error';\nimport {\n selectAllBetBuilderErrors,\n selectAllCurrentBetslipErrors,\n selectBetslipTypeErrorsFactory,\n selectCurrentBetslipTypeErrors,\n selectCurrentStakeError,\n selectPickErrorsState,\n} from '../validation/selectors';\nimport {\n isGroupPickOfferChange,\n isPickStatusClosed,\n isPickStatusLocked,\n isPickStatusOddsChangedPreCheck,\n isStakeError,\n isUnderMinimumStakeErrorPreCheck,\n} from '../validation/services/utils/betslip-errors-utils';\nimport { QuickBetUiState } from './quick-bet.state';\n\nexport const quickBetStateSelector = createSelector(betslipSelector, (state) => state.quickBet);\nexport const quickBetUiStateSelector = createSelector(quickBetStateSelector, (state) => state.uiState);\nexport const actionButtonStateSelector = createSelector(quickBetStateSelector, (state) => state.actionButtonState);\nexport const betPlacedResultSelector = createSelector(quickBetStateSelector, (state) => state.betPlacedResult);\nexport const showQuickBetHelpSelector = createSelector(quickBetStateSelector, (state) => state.helpState);\nexport const selectQuickBetPickIds = createSelector(quickBetStateSelector, (state) => state.pickIds);\nexport const selectQuickBetStakeState = createSelector(quickBetStateSelector, (state) => state.stakeState);\n\nexport const selectLinearQuickBetBuilderState = createSelector(\n quickBetStateSelector,\n selectIsLinearBetslip,\n (state, isLinear): { isLinearQuickBetBuilder: true; pickId: PickId } | { isLinearQuickBetBuilder: false; pickId: undefined } => {\n if (state.pickIds.length === 1 && isBetBuilderPickId(state.pickIds[0]) && isLinear) {\n return {\n isLinearQuickBetBuilder: true,\n pickId: state.pickIds[0],\n };\n }\n\n return {\n isLinearQuickBetBuilder: false,\n pickId: undefined,\n };\n },\n);\n\nexport const selectQuickBetContext = createSelector(\n betslipBaseSelector,\n selectQuickBetPickIds,\n selectIsLinearBetslip,\n (\n betslipBaseState,\n quickBetPickIds,\n isLinear,\n ): { betslipType: BetslipType.Combo } | { betslipType: BetslipType.BetBuilder | BetslipType.Single; pickId: PickId } => {\n //The betbuilder pick can come from the BB drawer OR from the QB itself\n const betBuilderPickId = quickBetPickIds.length === 1 && isBetBuilderPickId(quickBetPickIds[0]) ? quickBetPickIds[0] : null;\n if (betBuilderPickId) {\n // if it's a bet builder pick, and we're in linear mode - all bet builder picks are stored in BetBuilderState\n if (isLinear) {\n return { betslipType: BetslipType.BetBuilder, pickId: betBuilderPickId };\n }\n\n // if it's a Sportcast pick and isSportcastAsCombo is false then it is stored in Single state\n if (BetBuilderPickId.isId(betBuilderPickId) && !betslipBaseState.isSportcastAsComboEnabled) {\n return { betslipType: BetslipType.Single, pickId: betBuilderPickId };\n }\n\n // otherwise(it's GroupPick OR Sportcast with isSportcastAsCombo==true) then we should check the Combo state\n return { betslipType: BetslipType.Combo };\n }\n\n // if it's a \"regular\" single pick, then check the Single state\n if (quickBetPickIds.length === 1) {\n return { betslipType: BetslipType.Single, pickId: quickBetPickIds[0] };\n }\n\n // otherwise(multiple picks), check Combo state\n return { betslipType: BetslipType.Combo };\n },\n);\n\nexport const selectQuickBetRewardToken = createSelector(selectTokensStateContext, selectQuickBetContext, ({ tokens, types }, context) => {\n return getSelectedTokenForContext(context, tokens, types);\n});\n\nexport const selectIsLinearQuickBetBuilder = createSelector(selectLinearQuickBetBuilderState, (state) => state.isLinearQuickBetBuilder);\n\nexport const selectAllQuickBetErrors = createSelector(\n selectIsLinearQuickBetBuilder,\n selectAllCurrentBetslipErrors,\n selectAllBetBuilderErrors,\n (isLinearQuickBetBuilder, currentBetslipErrors, betbuilderErrors) => {\n return isLinearQuickBetBuilder ? betbuilderErrors : currentBetslipErrors;\n },\n);\n\nexport const selectIsQuickBetSuccess = createSelector(quickBetUiStateSelector, (state) => state === QuickBetUiState.Success);\nexport const selectIsQuickBetInOverlay = createSelector(quickBetStateSelector, (state) => state.inOverlay);\n\nexport const selectQuickBetTypeErrors = createSelector(\n selectIsLinearQuickBetBuilder,\n selectCurrentBetslipTypeErrors,\n selectBetslipTypeErrorsFactory(BetslipType.BetBuilder),\n (isLinearQuickBetBuilder, currentTypeErrors, betBuilderTypeErrors) => (isLinearQuickBetBuilder ? betBuilderTypeErrors : currentTypeErrors),\n);\n\nexport const selectQuickBetStakeError = createSelector(\n selectIsLinearQuickBetBuilder,\n selectCurrentStakeError,\n selectAllBetBuilderErrors,\n (isLinearQuickBetBuilder, currentStakeError, betBuilderErrors) =>\n isLinearQuickBetBuilder ? betBuilderErrors.find(isStakeError) : currentStakeError,\n);\n\nexport const selectQuickBetStake = createSelector(\n selectLinearQuickBetBuilderState,\n selectCurrentStake,\n selectBetBuilderPicksState,\n (state, currentStake, betBuilderPickState) =>\n state.isLinearQuickBetBuilder ? (betBuilderPickState.picks[state.pickId!.toString()]?.actualStake ?? null) : currentStake,\n);\n\nexport const selectQuickBetStakeValidationState = createSelector(selectQuickBetStake, selectQuickBetStakeError, (stake, stakeError) => ({\n stake,\n stakeError,\n}));\n\nexport const selectQuickBetPicksErrors = createSelector(\n selectPickErrorsState,\n betslipCurrentTypeSelector,\n selectIsLinearQuickBetBuilder,\n (pickErrors, type, isLinearQuickBetBuilder) => {\n if (isLinearQuickBetBuilder) {\n return pickErrors[BetslipType.BetBuilder];\n }\n\n return type ? pickErrors[type] : {};\n },\n);\n\nexport const selectQuickBetErrorsState = createSelector(\n selectAllQuickBetErrors,\n selectQuickBetPicksErrors,\n quickBetStateSelector,\n (betslipErrors, pickErrors, quickBetState) => {\n const pickIds = quickBetState.pickIds;\n const quickBetPickErrors = pickIds.flatMap((pickId) => pickErrors[pickId.toString()] ?? []);\n const oddsChangedError = quickBetPickErrors.find(isPickStatusOddsChangedPreCheck);\n\n return {\n hasBetslipErrors: betslipErrors.length > 0,\n isLocked: quickBetPickErrors.some((error) => isPickStatusLocked(error) || isGroupPickOfferChange(error)),\n isClosed: quickBetPickErrors.some((error) => isPickStatusClosed(error) || error instanceof GroupWithClosedLegsError),\n oddsChanged: !!oddsChangedError,\n oddsAccepted: !!oddsChangedError?.userHasBeenNotified,\n hasStakeUpdateErrors: betslipErrors.some(isUnderMinimumStakeErrorPreCheck),\n };\n },\n);\n\nexport const selectHasQuickBetRewardTokensErrors = createSelector(\n selectTokensStateContext,\n selectQuickBetContext,\n ({ eligibilityState, tokens, types }, rewardTokenContext) => {\n const [_, eligibility] = getSelectedTokenAndEligibilityForContext(rewardTokenContext, eligibilityState, tokens, types);\n\n if (!eligibility) {\n return false;\n }\n\n return [CriteriaType.SlipType, CriteriaType.MinimumLegs, CriteriaType.MaximumStake].some(\n // This needs to be checked against the boolean literal, as using \"!\" would also return \"true\"\n // when the criteria type is undefined. We only want to return \"true\" if the type is defined and false\n (criteriaType) => eligibility.softCriteriasValidity[criteriaType] === false,\n );\n },\n);\n\nexport const selectQuickBetErrorsStateWithRewardsTokens = createSelector(\n selectQuickBetErrorsState,\n selectHasQuickBetRewardTokensErrors,\n (state, hasRewardTokensError) => ({\n ...state,\n hasBetslipErrors: state.hasBetslipErrors || hasRewardTokensError,\n }),\n);\n\nexport const selectClosedLockedPickOverview = createSelector(\n betslipTypeStateSelector,\n betslipPicksListSelector,\n selectQuickBetPicksErrors,\n (typeState, pickList, comboPickErrors) => ({\n typeState,\n pickList,\n comboPickErrors,\n }),\n);\n\nexport const selectQuickBetLockedState = createSelector(selectQuickBetErrorsState, (state) => state.isLocked);\nexport const selectQuickBetLockedOrClosedState = createSelector(selectQuickBetErrorsState, (state) => {\n return { isLocked: state.isLocked, isClosed: state.isClosed };\n});\n\nexport const selectQuickBetActionButtonProcessing = createSelector(actionButtonStateSelector, (state) => state.isProcessing);\n\nexport const selectQuickBetPickNotificationState = createSelector(\n quickBetStateSelector,\n betslipPicksListSelector,\n settingsOddsAcceptanceSelector,\n selectQuickBetErrorsState,\n (quickBet, picksList, oddsAcceptance, errorState) => ({ quickBet, picksList, oddsAcceptance, errorState }),\n);\n\nexport const selectQuickBetStakeErrorState = createSelector(selectQuickBetStakeState, selectAllQuickBetErrors, (stakeState, errors) => ({\n stakeState,\n errors,\n}));\n\nexport const selectQuickBetPicks = createSelector(betslipPicksListSelector, selectQuickBetPickIds, (pickList, quickBetPickIds) => {\n const stringPickIds = quickBetPickIds.map((id) => id.toString());\n\n return pickList.filter((pick) => stringPickIds.includes(pick.id.toString()));\n});\n\nexport const selectQuickBetBetBuilderPicks = createSelector(selectQuickBetPickIds, betslipPicksListSelector, (quickBetPickIds, pickList) => {\n const betBuilderPickIds = [...quickBetPickIds].filter(BetBuilderPickId.isId).map((id) => id.toString());\n\n return pickList.filter((pick) => betBuilderPickIds.includes(pick.id.toString())) as BetslipBetBuilderPick[];\n});\n\nexport const selectQuickBetBetBuilderPicksCount = createSelector(selectQuickBetBetBuilderPicks, (betBuilderPicks) => betBuilderPicks.length);\n\nexport const selectQuickBetDisplayHelper = createSelector(quickBetStateSelector, (quickBetState) => quickBetState.displayHelper);\n\nexport const selectQuickBetLegsCount = createSelector(selectQuickBetPicks, (quickBetPicks) => getLegsCount(quickBetPicks));\n\nexport const selectedPickCountByCurrentType = createSelector(\n betslipCurrentTypeSelector,\n comboPickCountSelector,\n singlePickCountSelector,\n (currentType, comboCount, singleCount) => {\n if (currentType === BetslipType.Single) {\n return singleCount;\n }\n\n return comboCount;\n },\n);\n\nexport const selectQuickBetTrackingType = createSelector(betslipBaseSelector, selectQuickBetPickIds, (betslipBaseState, quickBetPickIds) => {\n const betBuilderPickId = getBetBuilderId(quickBetPickIds);\n if (betBuilderPickId) {\n if (BetBuilderPickId.isId(betBuilderPickId) && !betslipBaseState.isSportcastAsComboEnabled) {\n return PickBetTrackingTypes.Single;\n }\n\n return PickBetTrackingTypes.Sgp;\n }\n\n if (quickBetPickIds.length === 1) {\n return isBetBuilderPickId(quickBetPickIds[0]) ? PickBetTrackingTypes.Sgp : PickBetTrackingTypes.Single;\n }\n\n return quickBetPickIds.some(isBetBuilderPickId) ? PickBetTrackingTypes.SgpPlus : PickBetTrackingTypes.Parlay;\n});\n\nexport const selectTrackingDetails = createSelector(selectQuickBetTrackingType, selectedPickCountByCurrentType, (trackingType, pickCount) => {\n return {\n mode: trackingType,\n pickCount,\n };\n});\n\nexport const selectIsQuickBetActive = createSelector(quickBetUiStateSelector, (state) => state !== QuickBetUiState.None);\n\nexport const selectQuickBetPickIdsOrBetbuilderId = createSelector(selectQuickBetPickIds, (pickIds) => {\n return [...pickIds];\n});\n\nfunction getBetBuilderId(quickBetPickIds: PickId[]) {\n if (quickBetPickIds.length === 1 && isBetBuilderPickId(quickBetPickIds[0])) {\n return quickBetPickIds[0];\n }\n\n return null;\n}\n\nexport const selectQuickBetFreebetToken = createSelector(selectQuickBetRewardToken, (tokenInfo) => (isFreebetToken(tokenInfo) ? tokenInfo : null));\n\nexport const selectIsQuickBetOpen = createSelector(\n quickBetUiStateSelector,\n selectQuickBetDisplayHelper,\n (uiState, displayHelper) => uiState !== QuickBetUiState.None && !displayHelper?.hiddenByError,\n);\n\nexport const selectQuickBetPicksWithErrors = createSelector(selectQuickBetPicks, selectQuickBetPicksErrors, (pickList, pickErrors) =>\n pickList.map((pick) => ({\n pick,\n errors: pickErrors[pick.id.toString()] || [],\n })),\n);\nexport const selectQuickBetUnderMinStakeError = createSelector(\n selectAllQuickBetErrors,\n (betslipErrors) => betslipErrors.find(isUnderMinimumStakeErrorPreCheck) ?? null,\n);\n","import { DOCUMENT } from '@angular/common';\nimport { Injectable, Signal, inject } from '@angular/core';\n\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { Store } from '@ngrx/store';\nimport { selectIsQuickBetSuccess } from 'packages/sports/common/betslip/modules/quick-bet/quick-bet.selectors';\nimport { IQuickBetState } from 'packages/sports/common/betslip/modules/quick-bet/quick-bet.state';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class HideHeaderService {\n scrollLastPosition = 0;\n private quickBetUiState: Signal;\n\n private readonly _doc = inject(DOCUMENT);\n\n constructor(\n private scrollContainer: ScrollContainerService,\n private store: Store,\n ) {\n this.quickBetUiState = this.store.selectSignal(selectIsQuickBetSuccess);\n }\n\n onScroll() {\n if (this.quickBetUiState()) {\n return;\n }\n\n if (this.scrollContainer.scrollTop > 5 && this.scrollContainer.scrollTop >= this.scrollLastPosition) {\n const headerElement = this._doc.querySelector('.slot-header_bottom_items') as HTMLElement;\n const scrollHeight = headerElement.offsetTop;\n const scrollTop = (Math.abs(scrollHeight) * -1).toString();\n this._doc.querySelector('.slot-header')?.setAttribute('style', `top:${scrollTop}px`);\n } else {\n this._doc.querySelector('.slot-header')?.removeAttribute('style');\n }\n this.scrollLastPosition = this.scrollContainer.scrollTop;\n }\n\n removeHideHeader() {\n this._doc.querySelector('.slot-header')?.removeAttribute('style');\n }\n}\n","import { ImageProfile } from '@cds/betting-offer';\nimport { CompetitionStatistics } from '@cds/statistics/competition';\nimport { EventModel, EventOptionGroup, RegionModel, SportModel } from '@frontend/sports/betting-offer/feature/model';\nimport { StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\n\nexport enum SubscriptionTopic {\n Grid,\n NonGridable,\n Specials,\n Outrights,\n All,\n}\nexport interface Column {\n id: string;\n groups: Group[];\n enabled: boolean;\n more: boolean;\n}\n\nexport interface OptionsToShow {\n v1?: number[];\n v2?: number[];\n}\n\nexport interface Group {\n id: string;\n name: string;\n active: boolean;\n extended: boolean;\n visible: boolean;\n balancedMarket?: boolean;\n marketAttribute?: boolean;\n options?: string[];\n childGroups?: string[];\n optionsToShow?: OptionsToShow;\n badge?: GroupBadge;\n scoreBoardPeriodId?: number;\n fallbackGridGroupIds?: string[];\n isFallbackGroup: boolean;\n}\n\nexport enum GridLayout {\n Default,\n SixPack,\n Hybrid,\n}\n\nexport interface GridBreakpointSize {\n columns: number;\n default: number;\n}\n\nexport enum GridGrouping {\n None,\n Date,\n League,\n HomeForm,\n AwayForm,\n OverallForm,\n}\n\nexport interface GroupBadge {\n text: string;\n cssClass: string;\n}\n\nexport enum GridSorting {\n Competition,\n Time,\n HomeForm,\n AwayForm,\n OverallForm,\n}\n\nexport interface CollapsedState {\n collapsed: boolean;\n collapsedChildren: string[];\n}\n\nexport interface GridEvent extends EventModel {\n favourited?: boolean;\n}\n\nexport interface EventGroup {\n id: number;\n name: string;\n count: number;\n events: GridEvent[];\n collapsed: boolean;\n collapsible: boolean;\n deferred: boolean;\n}\n\nexport interface LeagueGroup extends EventGroup {\n siblings: number[];\n region: RegionModel;\n canBeFavourited: boolean;\n statistics?: CompetitionStatistics;\n virtualCompetitionId?: number;\n virtualCompetitionGroupId?: number;\n imageProfile?: ImageProfile;\n}\n\nexport interface StandingsData {\n groups: LeagueGroup[];\n virtualCompetitionId?: number;\n}\n\nexport interface DateGroup extends EventGroup {\n date: Date;\n}\n\nexport interface FormGroup extends EventGroup {\n winCount: number;\n}\n\nexport interface GridMedia {\n videoEvent?: string;\n animationEvent?: string;\n statsEvent?: string;\n enabled: boolean;\n}\n\nexport interface GridCollapsedModel {\n groups: number[];\n events: StringDictionary;\n}\n\nexport interface GridModel {\n columns: Column[];\n grouping: GridGrouping;\n disableGroupSorting?: boolean;\n groupingThreshold?: number;\n groups: (EventGroup | LeagueGroup)[];\n id: string;\n layout: GridLayout;\n sport: SportModel;\n media: GridMedia;\n topic: SubscriptionTopic;\n sorting?: GridSorting;\n collapsed: GridCollapsedModel;\n}\n\nexport interface FallbackGroup {\n optionGroup: EventOptionGroup | undefined;\n fallbackMarketName: string;\n showNoGoalText: boolean;\n isFallbackMarket: boolean;\n}\n","/**\r\n * A collection of shims that provide minimal functionality of the ES6 collections.\r\n *\r\n * These implementations are not meant to be used outside of the ResizeObserver\r\n * modules as they cover only a limited range of use cases.\r\n */\n/* eslint-disable require-jsdoc, valid-jsdoc */\nvar MapShim = function () {\n if (typeof Map !== 'undefined') {\n return Map;\n }\n /**\r\n * Returns index in provided array that matches the specified key.\r\n *\r\n * @param {Array} arr\r\n * @param {*} key\r\n * @returns {number}\r\n */\n function getIndex(arr, key) {\n var result = -1;\n arr.some(function (entry, index) {\n if (entry[0] === key) {\n result = index;\n return true;\n }\n return false;\n });\n return result;\n }\n return /** @class */function () {\n function class_1() {\n this.__entries__ = [];\n }\n Object.defineProperty(class_1.prototype, \"size\", {\n /**\r\n * @returns {boolean}\r\n */\n get: function () {\n return this.__entries__.length;\n },\n enumerable: true,\n configurable: true\n });\n /**\r\n * @param {*} key\r\n * @returns {*}\r\n */\n class_1.prototype.get = function (key) {\n var index = getIndex(this.__entries__, key);\n var entry = this.__entries__[index];\n return entry && entry[1];\n };\n /**\r\n * @param {*} key\r\n * @param {*} value\r\n * @returns {void}\r\n */\n class_1.prototype.set = function (key, value) {\n var index = getIndex(this.__entries__, key);\n if (~index) {\n this.__entries__[index][1] = value;\n } else {\n this.__entries__.push([key, value]);\n }\n };\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\n class_1.prototype.delete = function (key) {\n var entries = this.__entries__;\n var index = getIndex(entries, key);\n if (~index) {\n entries.splice(index, 1);\n }\n };\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\n class_1.prototype.has = function (key) {\n return !!~getIndex(this.__entries__, key);\n };\n /**\r\n * @returns {void}\r\n */\n class_1.prototype.clear = function () {\n this.__entries__.splice(0);\n };\n /**\r\n * @param {Function} callback\r\n * @param {*} [ctx=null]\r\n * @returns {void}\r\n */\n class_1.prototype.forEach = function (callback, ctx) {\n if (ctx === void 0) {\n ctx = null;\n }\n for (var _i = 0, _a = this.__entries__; _i < _a.length; _i++) {\n var entry = _a[_i];\n callback.call(ctx, entry[1], entry[0]);\n }\n };\n return class_1;\n }();\n}();\n\n/**\r\n * Detects whether window and document objects are available in current environment.\r\n */\nvar isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document;\n\n// Returns global object of a current environment.\nvar global$1 = function () {\n if (typeof global !== 'undefined' && global.Math === Math) {\n return global;\n }\n if (typeof self !== 'undefined' && self.Math === Math) {\n return self;\n }\n if (typeof window !== 'undefined' && window.Math === Math) {\n return window;\n }\n // eslint-disable-next-line no-new-func\n return Function('return this')();\n}();\n\n/**\r\n * A shim for the requestAnimationFrame which falls back to the setTimeout if\r\n * first one is not supported.\r\n *\r\n * @returns {number} Requests' identifier.\r\n */\nvar requestAnimationFrame$1 = function () {\n if (typeof requestAnimationFrame === 'function') {\n // It's required to use a bounded function because IE sometimes throws\n // an \"Invalid calling object\" error if rAF is invoked without the global\n // object on the left hand side.\n return requestAnimationFrame.bind(global$1);\n }\n return function (callback) {\n return setTimeout(function () {\n return callback(Date.now());\n }, 1000 / 60);\n };\n}();\n\n// Defines minimum timeout before adding a trailing call.\nvar trailingTimeout = 2;\n/**\r\n * Creates a wrapper function which ensures that provided callback will be\r\n * invoked only once during the specified delay period.\r\n *\r\n * @param {Function} callback - Function to be invoked after the delay period.\r\n * @param {number} delay - Delay after which to invoke callback.\r\n * @returns {Function}\r\n */\nfunction throttle(callback, delay) {\n var leadingCall = false,\n trailingCall = false,\n lastCallTime = 0;\n /**\r\n * Invokes the original callback function and schedules new invocation if\r\n * the \"proxy\" was called during current request.\r\n *\r\n * @returns {void}\r\n */\n function resolvePending() {\n if (leadingCall) {\n leadingCall = false;\n callback();\n }\n if (trailingCall) {\n proxy();\n }\n }\n /**\r\n * Callback invoked after the specified delay. It will further postpone\r\n * invocation of the original function delegating it to the\r\n * requestAnimationFrame.\r\n *\r\n * @returns {void}\r\n */\n function timeoutCallback() {\n requestAnimationFrame$1(resolvePending);\n }\n /**\r\n * Schedules invocation of the original function.\r\n *\r\n * @returns {void}\r\n */\n function proxy() {\n var timeStamp = Date.now();\n if (leadingCall) {\n // Reject immediately following calls.\n if (timeStamp - lastCallTime < trailingTimeout) {\n return;\n }\n // Schedule new call to be in invoked when the pending one is resolved.\n // This is important for \"transitions\" which never actually start\n // immediately so there is a chance that we might miss one if change\n // happens amids the pending invocation.\n trailingCall = true;\n } else {\n leadingCall = true;\n trailingCall = false;\n setTimeout(timeoutCallback, delay);\n }\n lastCallTime = timeStamp;\n }\n return proxy;\n}\n\n// Minimum delay before invoking the update of observers.\nvar REFRESH_DELAY = 20;\n// A list of substrings of CSS properties used to find transition events that\n// might affect dimensions of observed elements.\nvar transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];\n// Check if MutationObserver is available.\nvar mutationObserverSupported = typeof MutationObserver !== 'undefined';\n/**\r\n * Singleton controller class which handles updates of ResizeObserver instances.\r\n */\nvar ResizeObserverController = /** @class */function () {\n /**\r\n * Creates a new instance of ResizeObserverController.\r\n *\r\n * @private\r\n */\n function ResizeObserverController() {\n /**\r\n * Indicates whether DOM listeners have been added.\r\n *\r\n * @private {boolean}\r\n */\n this.connected_ = false;\n /**\r\n * Tells that controller has subscribed for Mutation Events.\r\n *\r\n * @private {boolean}\r\n */\n this.mutationEventsAdded_ = false;\n /**\r\n * Keeps reference to the instance of MutationObserver.\r\n *\r\n * @private {MutationObserver}\r\n */\n this.mutationsObserver_ = null;\n /**\r\n * A list of connected observers.\r\n *\r\n * @private {Array}\r\n */\n this.observers_ = [];\n this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);\n this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);\n }\n /**\r\n * Adds observer to observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be added.\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.addObserver = function (observer) {\n if (!~this.observers_.indexOf(observer)) {\n this.observers_.push(observer);\n }\n // Add listeners if they haven't been added yet.\n if (!this.connected_) {\n this.connect_();\n }\n };\n /**\r\n * Removes observer from observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be removed.\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.removeObserver = function (observer) {\n var observers = this.observers_;\n var index = observers.indexOf(observer);\n // Remove observer if it's present in registry.\n if (~index) {\n observers.splice(index, 1);\n }\n // Remove listeners if controller has no connected observers.\n if (!observers.length && this.connected_) {\n this.disconnect_();\n }\n };\n /**\r\n * Invokes the update of observers. It will continue running updates insofar\r\n * it detects changes.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.refresh = function () {\n var changesDetected = this.updateObservers_();\n // Continue running updates if changes have been detected as there might\n // be future ones caused by CSS transitions.\n if (changesDetected) {\n this.refresh();\n }\n };\n /**\r\n * Updates every observer from observers list and notifies them of queued\r\n * entries.\r\n *\r\n * @private\r\n * @returns {boolean} Returns \"true\" if any observer has detected changes in\r\n * dimensions of it's elements.\r\n */\n ResizeObserverController.prototype.updateObservers_ = function () {\n // Collect observers that have active observations.\n var activeObservers = this.observers_.filter(function (observer) {\n return observer.gatherActive(), observer.hasActive();\n });\n // Deliver notifications in a separate cycle in order to avoid any\n // collisions between observers, e.g. when multiple instances of\n // ResizeObserver are tracking the same element and the callback of one\n // of them changes content dimensions of the observed target. Sometimes\n // this may result in notifications being blocked for the rest of observers.\n activeObservers.forEach(function (observer) {\n return observer.broadcastActive();\n });\n return activeObservers.length > 0;\n };\n /**\r\n * Initializes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.connect_ = function () {\n // Do nothing if running in a non-browser environment or if listeners\n // have been already added.\n if (!isBrowser || this.connected_) {\n return;\n }\n // Subscription to the \"Transitionend\" event is used as a workaround for\n // delayed transitions. This way it's possible to capture at least the\n // final state of an element.\n document.addEventListener('transitionend', this.onTransitionEnd_);\n window.addEventListener('resize', this.refresh);\n if (mutationObserverSupported) {\n this.mutationsObserver_ = new MutationObserver(this.refresh);\n this.mutationsObserver_.observe(document, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true\n });\n } else {\n document.addEventListener('DOMSubtreeModified', this.refresh);\n this.mutationEventsAdded_ = true;\n }\n this.connected_ = true;\n };\n /**\r\n * Removes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.disconnect_ = function () {\n // Do nothing if running in a non-browser environment or if listeners\n // have been already removed.\n if (!isBrowser || !this.connected_) {\n return;\n }\n document.removeEventListener('transitionend', this.onTransitionEnd_);\n window.removeEventListener('resize', this.refresh);\n if (this.mutationsObserver_) {\n this.mutationsObserver_.disconnect();\n }\n if (this.mutationEventsAdded_) {\n document.removeEventListener('DOMSubtreeModified', this.refresh);\n }\n this.mutationsObserver_ = null;\n this.mutationEventsAdded_ = false;\n this.connected_ = false;\n };\n /**\r\n * \"Transitionend\" event handler.\r\n *\r\n * @private\r\n * @param {TransitionEvent} event\r\n * @returns {void}\r\n */\n ResizeObserverController.prototype.onTransitionEnd_ = function (_a) {\n var _b = _a.propertyName,\n propertyName = _b === void 0 ? '' : _b;\n // Detect whether transition may affect dimensions of an element.\n var isReflowProperty = transitionKeys.some(function (key) {\n return !!~propertyName.indexOf(key);\n });\n if (isReflowProperty) {\n this.refresh();\n }\n };\n /**\r\n * Returns instance of the ResizeObserverController.\r\n *\r\n * @returns {ResizeObserverController}\r\n */\n ResizeObserverController.getInstance = function () {\n if (!this.instance_) {\n this.instance_ = new ResizeObserverController();\n }\n return this.instance_;\n };\n /**\r\n * Holds reference to the controller's instance.\r\n *\r\n * @private {ResizeObserverController}\r\n */\n ResizeObserverController.instance_ = null;\n return ResizeObserverController;\n}();\n\n/**\r\n * Defines non-writable/enumerable properties of the provided target object.\r\n *\r\n * @param {Object} target - Object for which to define properties.\r\n * @param {Object} props - Properties to be defined.\r\n * @returns {Object} Target object.\r\n */\nvar defineConfigurable = function (target, props) {\n for (var _i = 0, _a = Object.keys(props); _i < _a.length; _i++) {\n var key = _a[_i];\n Object.defineProperty(target, key, {\n value: props[key],\n enumerable: false,\n writable: false,\n configurable: true\n });\n }\n return target;\n};\n\n/**\r\n * Returns the global object associated with provided element.\r\n *\r\n * @param {Object} target\r\n * @returns {Object}\r\n */\nvar getWindowOf = function (target) {\n // Assume that the element is an instance of Node, which means that it\n // has the \"ownerDocument\" property from which we can retrieve a\n // corresponding global object.\n var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;\n // Return the local global object if it's not possible extract one from\n // provided element.\n return ownerGlobal || global$1;\n};\n\n// Placeholder of an empty content rectangle.\nvar emptyRect = createRectInit(0, 0, 0, 0);\n/**\r\n * Converts provided string to a number.\r\n *\r\n * @param {number|string} value\r\n * @returns {number}\r\n */\nfunction toFloat(value) {\n return parseFloat(value) || 0;\n}\n/**\r\n * Extracts borders size from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @param {...string} positions - Borders positions (top, right, ...)\r\n * @returns {number}\r\n */\nfunction getBordersSize(styles) {\n var positions = [];\n for (var _i = 1; _i < arguments.length; _i++) {\n positions[_i - 1] = arguments[_i];\n }\n return positions.reduce(function (size, position) {\n var value = styles['border-' + position + '-width'];\n return size + toFloat(value);\n }, 0);\n}\n/**\r\n * Extracts paddings sizes from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @returns {Object} Paddings box.\r\n */\nfunction getPaddings(styles) {\n var positions = ['top', 'right', 'bottom', 'left'];\n var paddings = {};\n for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {\n var position = positions_1[_i];\n var value = styles['padding-' + position];\n paddings[position] = toFloat(value);\n }\n return paddings;\n}\n/**\r\n * Calculates content rectangle of provided SVG element.\r\n *\r\n * @param {SVGGraphicsElement} target - Element content rectangle of which needs\r\n * to be calculated.\r\n * @returns {DOMRectInit}\r\n */\nfunction getSVGContentRect(target) {\n var bbox = target.getBBox();\n return createRectInit(0, 0, bbox.width, bbox.height);\n}\n/**\r\n * Calculates content rectangle of provided HTMLElement.\r\n *\r\n * @param {HTMLElement} target - Element for which to calculate the content rectangle.\r\n * @returns {DOMRectInit}\r\n */\nfunction getHTMLElementContentRect(target) {\n // Client width & height properties can't be\n // used exclusively as they provide rounded values.\n var clientWidth = target.clientWidth,\n clientHeight = target.clientHeight;\n // By this condition we can catch all non-replaced inline, hidden and\n // detached elements. Though elements with width & height properties less\n // than 0.5 will be discarded as well.\n //\n // Without it we would need to implement separate methods for each of\n // those cases and it's not possible to perform a precise and performance\n // effective test for hidden elements. E.g. even jQuery's ':visible' filter\n // gives wrong results for elements with width & height less than 0.5.\n if (!clientWidth && !clientHeight) {\n return emptyRect;\n }\n var styles = getWindowOf(target).getComputedStyle(target);\n var paddings = getPaddings(styles);\n var horizPad = paddings.left + paddings.right;\n var vertPad = paddings.top + paddings.bottom;\n // Computed styles of width & height are being used because they are the\n // only dimensions available to JS that contain non-rounded values. It could\n // be possible to utilize the getBoundingClientRect if only it's data wasn't\n // affected by CSS transformations let alone paddings, borders and scroll bars.\n var width = toFloat(styles.width),\n height = toFloat(styles.height);\n // Width & height include paddings and borders when the 'border-box' box\n // model is applied (except for IE).\n if (styles.boxSizing === 'border-box') {\n // Following conditions are required to handle Internet Explorer which\n // doesn't include paddings and borders to computed CSS dimensions.\n //\n // We can say that if CSS dimensions + paddings are equal to the \"client\"\n // properties then it's either IE, and thus we don't need to subtract\n // anything, or an element merely doesn't have paddings/borders styles.\n if (Math.round(width + horizPad) !== clientWidth) {\n width -= getBordersSize(styles, 'left', 'right') + horizPad;\n }\n if (Math.round(height + vertPad) !== clientHeight) {\n height -= getBordersSize(styles, 'top', 'bottom') + vertPad;\n }\n }\n // Following steps can't be applied to the document's root element as its\n // client[Width/Height] properties represent viewport area of the window.\n // Besides, it's as well not necessary as the itself neither has\n // rendered scroll bars nor it can be clipped.\n if (!isDocumentElement(target)) {\n // In some browsers (only in Firefox, actually) CSS width & height\n // include scroll bars size which can be removed at this step as scroll\n // bars are the only difference between rounded dimensions + paddings\n // and \"client\" properties, though that is not always true in Chrome.\n var vertScrollbar = Math.round(width + horizPad) - clientWidth;\n var horizScrollbar = Math.round(height + vertPad) - clientHeight;\n // Chrome has a rather weird rounding of \"client\" properties.\n // E.g. for an element with content width of 314.2px it sometimes gives\n // the client width of 315px and for the width of 314.7px it may give\n // 314px. And it doesn't happen all the time. So just ignore this delta\n // as a non-relevant.\n if (Math.abs(vertScrollbar) !== 1) {\n width -= vertScrollbar;\n }\n if (Math.abs(horizScrollbar) !== 1) {\n height -= horizScrollbar;\n }\n }\n return createRectInit(paddings.left, paddings.top, width, height);\n}\n/**\r\n * Checks whether provided element is an instance of the SVGGraphicsElement.\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\nvar isSVGGraphicsElement = function () {\n // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement\n // interface.\n if (typeof SVGGraphicsElement !== 'undefined') {\n return function (target) {\n return target instanceof getWindowOf(target).SVGGraphicsElement;\n };\n }\n // If it's so, then check that element is at least an instance of the\n // SVGElement and that it has the \"getBBox\" method.\n // eslint-disable-next-line no-extra-parens\n return function (target) {\n return target instanceof getWindowOf(target).SVGElement && typeof target.getBBox === 'function';\n };\n}();\n/**\r\n * Checks whether provided element is a document element ().\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\nfunction isDocumentElement(target) {\n return target === getWindowOf(target).document.documentElement;\n}\n/**\r\n * Calculates an appropriate content rectangle for provided html or svg element.\r\n *\r\n * @param {Element} target - Element content rectangle of which needs to be calculated.\r\n * @returns {DOMRectInit}\r\n */\nfunction getContentRect(target) {\n if (!isBrowser) {\n return emptyRect;\n }\n if (isSVGGraphicsElement(target)) {\n return getSVGContentRect(target);\n }\n return getHTMLElementContentRect(target);\n}\n/**\r\n * Creates rectangle with an interface of the DOMRectReadOnly.\r\n * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly\r\n *\r\n * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions.\r\n * @returns {DOMRectReadOnly}\r\n */\nfunction createReadOnlyRect(_a) {\n var x = _a.x,\n y = _a.y,\n width = _a.width,\n height = _a.height;\n // If DOMRectReadOnly is available use it as a prototype for the rectangle.\n var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object;\n var rect = Object.create(Constr.prototype);\n // Rectangle's properties are not writable and non-enumerable.\n defineConfigurable(rect, {\n x: x,\n y: y,\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: height + y,\n left: x\n });\n return rect;\n}\n/**\r\n * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates.\r\n * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit\r\n *\r\n * @param {number} x - X coordinate.\r\n * @param {number} y - Y coordinate.\r\n * @param {number} width - Rectangle's width.\r\n * @param {number} height - Rectangle's height.\r\n * @returns {DOMRectInit}\r\n */\nfunction createRectInit(x, y, width, height) {\n return {\n x: x,\n y: y,\n width: width,\n height: height\n };\n}\n\n/**\r\n * Class that is responsible for computations of the content rectangle of\r\n * provided DOM element and for keeping track of it's changes.\r\n */\nvar ResizeObservation = /** @class */function () {\n /**\r\n * Creates an instance of ResizeObservation.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n */\n function ResizeObservation(target) {\n /**\r\n * Broadcasted width of content rectangle.\r\n *\r\n * @type {number}\r\n */\n this.broadcastWidth = 0;\n /**\r\n * Broadcasted height of content rectangle.\r\n *\r\n * @type {number}\r\n */\n this.broadcastHeight = 0;\n /**\r\n * Reference to the last observed content rectangle.\r\n *\r\n * @private {DOMRectInit}\r\n */\n this.contentRect_ = createRectInit(0, 0, 0, 0);\n this.target = target;\n }\n /**\r\n * Updates content rectangle and tells whether it's width or height properties\r\n * have changed since the last broadcast.\r\n *\r\n * @returns {boolean}\r\n */\n ResizeObservation.prototype.isActive = function () {\n var rect = getContentRect(this.target);\n this.contentRect_ = rect;\n return rect.width !== this.broadcastWidth || rect.height !== this.broadcastHeight;\n };\n /**\r\n * Updates 'broadcastWidth' and 'broadcastHeight' properties with a data\r\n * from the corresponding properties of the last observed content rectangle.\r\n *\r\n * @returns {DOMRectInit} Last observed content rectangle.\r\n */\n ResizeObservation.prototype.broadcastRect = function () {\n var rect = this.contentRect_;\n this.broadcastWidth = rect.width;\n this.broadcastHeight = rect.height;\n return rect;\n };\n return ResizeObservation;\n}();\nvar ResizeObserverEntry = /** @class */function () {\n /**\r\n * Creates an instance of ResizeObserverEntry.\r\n *\r\n * @param {Element} target - Element that is being observed.\r\n * @param {DOMRectInit} rectInit - Data of the element's content rectangle.\r\n */\n function ResizeObserverEntry(target, rectInit) {\n var contentRect = createReadOnlyRect(rectInit);\n // According to the specification following properties are not writable\n // and are also not enumerable in the native implementation.\n //\n // Property accessors are not being used as they'd require to define a\n // private WeakMap storage which may cause memory leaks in browsers that\n // don't support this type of collections.\n defineConfigurable(this, {\n target: target,\n contentRect: contentRect\n });\n }\n return ResizeObserverEntry;\n}();\nvar ResizeObserverSPI = /** @class */function () {\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback function that is invoked\r\n * when one of the observed elements changes it's content dimensions.\r\n * @param {ResizeObserverController} controller - Controller instance which\r\n * is responsible for the updates of observer.\r\n * @param {ResizeObserver} callbackCtx - Reference to the public\r\n * ResizeObserver instance which will be passed to callback function.\r\n */\n function ResizeObserverSPI(callback, controller, callbackCtx) {\n /**\r\n * Collection of resize observations that have detected changes in dimensions\r\n * of elements.\r\n *\r\n * @private {Array}\r\n */\n this.activeObservations_ = [];\n /**\r\n * Registry of the ResizeObservation instances.\r\n *\r\n * @private {Map}\r\n */\n this.observations_ = new MapShim();\n if (typeof callback !== 'function') {\n throw new TypeError('The callback provided as parameter 1 is not a function.');\n }\n this.callback_ = callback;\n this.controller_ = controller;\n this.callbackCtx_ = callbackCtx;\n }\n /**\r\n * Starts observing provided element.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.observe = function (target) {\n if (!arguments.length) {\n throw new TypeError('1 argument required, but only 0 present.');\n }\n // Do nothing if current environment doesn't have the Element interface.\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\n return;\n }\n if (!(target instanceof getWindowOf(target).Element)) {\n throw new TypeError('parameter 1 is not of type \"Element\".');\n }\n var observations = this.observations_;\n // Do nothing if element is already being observed.\n if (observations.has(target)) {\n return;\n }\n observations.set(target, new ResizeObservation(target));\n this.controller_.addObserver(this);\n // Force the update of observations.\n this.controller_.refresh();\n };\n /**\r\n * Stops observing provided element.\r\n *\r\n * @param {Element} target - Element to stop observing.\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.unobserve = function (target) {\n if (!arguments.length) {\n throw new TypeError('1 argument required, but only 0 present.');\n }\n // Do nothing if current environment doesn't have the Element interface.\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\n return;\n }\n if (!(target instanceof getWindowOf(target).Element)) {\n throw new TypeError('parameter 1 is not of type \"Element\".');\n }\n var observations = this.observations_;\n // Do nothing if element is not being observed.\n if (!observations.has(target)) {\n return;\n }\n observations.delete(target);\n if (!observations.size) {\n this.controller_.removeObserver(this);\n }\n };\n /**\r\n * Stops observing all elements.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.disconnect = function () {\n this.clearActive();\n this.observations_.clear();\n this.controller_.removeObserver(this);\n };\n /**\r\n * Collects observation instances the associated element of which has changed\r\n * it's content rectangle.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.gatherActive = function () {\n var _this = this;\n this.clearActive();\n this.observations_.forEach(function (observation) {\n if (observation.isActive()) {\n _this.activeObservations_.push(observation);\n }\n });\n };\n /**\r\n * Invokes initial callback function with a list of ResizeObserverEntry\r\n * instances collected from active resize observations.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.broadcastActive = function () {\n // Do nothing if observer doesn't have active observations.\n if (!this.hasActive()) {\n return;\n }\n var ctx = this.callbackCtx_;\n // Create ResizeObserverEntry instance for every active observation.\n var entries = this.activeObservations_.map(function (observation) {\n return new ResizeObserverEntry(observation.target, observation.broadcastRect());\n });\n this.callback_.call(ctx, entries, ctx);\n this.clearActive();\n };\n /**\r\n * Clears the collection of active observations.\r\n *\r\n * @returns {void}\r\n */\n ResizeObserverSPI.prototype.clearActive = function () {\n this.activeObservations_.splice(0);\n };\n /**\r\n * Tells whether observer has active observations.\r\n *\r\n * @returns {boolean}\r\n */\n ResizeObserverSPI.prototype.hasActive = function () {\n return this.activeObservations_.length > 0;\n };\n return ResizeObserverSPI;\n}();\n\n// Registry of internal observers. If WeakMap is not available use current shim\n// for the Map collection as it has all required methods and because WeakMap\n// can't be fully polyfilled anyway.\nvar observers = typeof WeakMap !== 'undefined' ? new WeakMap() : new MapShim();\n/**\r\n * ResizeObserver API. Encapsulates the ResizeObserver SPI implementation\r\n * exposing only those methods and properties that are defined in the spec.\r\n */\nvar ResizeObserver = /** @class */function () {\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback that is invoked when\r\n * dimensions of the observed elements change.\r\n */\n function ResizeObserver(callback) {\n if (!(this instanceof ResizeObserver)) {\n throw new TypeError('Cannot call a class as a function.');\n }\n if (!arguments.length) {\n throw new TypeError('1 argument required, but only 0 present.');\n }\n var controller = ResizeObserverController.getInstance();\n var observer = new ResizeObserverSPI(callback, controller, this);\n observers.set(this, observer);\n }\n return ResizeObserver;\n}();\n// Expose public methods of ResizeObserver.\n['observe', 'unobserve', 'disconnect'].forEach(function (method) {\n ResizeObserver.prototype[method] = function () {\n var _a;\n return (_a = observers.get(this))[method].apply(_a, arguments);\n };\n});\nvar index = function () {\n // Export existing implementation if available.\n if (typeof global$1.ResizeObserver !== 'undefined') {\n return global$1.ResizeObserver;\n }\n return ResizeObserver;\n}();\nexport default index;","import { ElementRef, Injectable } from '@angular/core';\n\nimport ResizeObserver from 'resize-observer-polyfill';\nimport { Observable, Subject } from 'rxjs';\nimport { shareReplay } from 'rxjs/operators';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class ResizeObserverService {\n private changes$ = new Subject();\n private elementChanges = new WeakMap, Observable>();\n private observer = new ResizeObserver((entries: ResizeObserverEntry[]) => this.changes$.next(entries));\n\n observe(element: ElementRef): Observable {\n let elementChanges$ = this.elementChanges.get(element);\n if (!elementChanges$) {\n this.observer.observe(element.nativeElement);\n\n elementChanges$ = new Observable((observer) => {\n const inner = this.changes$.subscribe((changes) => {\n const change = changes.find((e) => e.target === element.nativeElement);\n if (change) {\n observer.next(change);\n }\n });\n\n return () => {\n inner.unsubscribe();\n this.observer.unobserve(element.nativeElement);\n this.elementChanges.delete(element);\n };\n }).pipe(shareReplay({ bufferSize: 1, refCount: true }));\n\n this.elementChanges.set(element, elementChanges$);\n }\n\n return elementChanges$;\n }\n}\n","import { DestroyRef, ElementRef, Injectable, Optional, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { ResizeObserverService } from '@frontend/sports/common/core/utils/dom';\nimport { GridBreakpointSize, GridLayout } from '@frontend/sports/grid/core/feature/model';\nimport { MediaQueryService } from '@frontend/vanilla/core';\nimport { entries, isEqual } from 'lodash-es';\nimport { Observable } from 'rxjs';\nimport { distinctUntilChanged, map } from 'rxjs/operators';\n\ninterface ColumnsBreakpoint {\n query: string;\n min?: number;\n max?: number;\n columns: number;\n}\n\ninterface GridBreakpoint {\n [breakpoint: string]: number;\n}\n\nexport interface GridBreakpointOptions {\n calculateGridWidth: (containerWidth: number) => number;\n}\n\nconst DEFAULT_GRID_BREAKPOINTS_OPTIONS: GridBreakpointOptions = {\n calculateGridWidth: (containerWidth: number) => containerWidth,\n};\n\nconst DEFAULT_GRID_BREAKPOINTS: GridBreakpoint = {\n 'xs': 1,\n 'sm': 2,\n 'md': 3,\n 'gt-md': 4,\n};\n\nconst SIX_PACK_GRID_BREAKPOINTS: GridBreakpoint = {\n 'lt-md': 1,\n 'gt-sm': 2,\n};\n\n@Injectable()\nexport class GridBreakpointService {\n private static DEFAULT_BREAKPOINTS: ColumnsBreakpoint[];\n private static SIX_PACK_BREAKPOINTS: ColumnsBreakpoint[];\n\n private get defaultBreakpoints(): ColumnsBreakpoint[] {\n return (\n GridBreakpointService.DEFAULT_BREAKPOINTS ??\n (GridBreakpointService.DEFAULT_BREAKPOINTS = this.parseGridBreakpoints(DEFAULT_GRID_BREAKPOINTS))\n );\n }\n\n private get sixPackBreakpoints(): ColumnsBreakpoint[] {\n return (\n GridBreakpointService.SIX_PACK_BREAKPOINTS ??\n (GridBreakpointService.SIX_PACK_BREAKPOINTS = this.parseGridBreakpoints(SIX_PACK_GRID_BREAKPOINTS))\n );\n }\n\n private readonly destroyRef = inject(DestroyRef);\n\n constructor(\n private mediaObserver: MediaQueryService,\n private resizeObserver: ResizeObserverService,\n @Optional() private container?: ElementRef,\n ) {}\n\n forGrid(\n element: ElementRef,\n layout: GridLayout,\n options: GridBreakpointOptions = DEFAULT_GRID_BREAKPOINTS_OPTIONS,\n ): Observable {\n const containerWidth = this.container\n ? this.resizeObserver.observe(this.container).pipe(\n map((changes) => changes.contentRect.width),\n distinctUntilChanged(),\n map((width) => options.calculateGridWidth(width)),\n )\n : this.mediaObserver.observe().pipe(map(() => element.nativeElement.clientWidth));\n\n return containerWidth.pipe(\n map((width) => {\n const columnsBreakpoints = layout === GridLayout.SixPack ? this.sixPackBreakpoints : this.defaultBreakpoints;\n\n return {\n default: this.matchColumnsBreakpoint(this.defaultBreakpoints, width),\n columns: this.matchColumnsBreakpoint(columnsBreakpoints, width),\n };\n }),\n distinctUntilChanged(isEqual),\n takeUntilDestroyed(this.destroyRef),\n );\n }\n\n private matchColumnsBreakpoint(source: ColumnsBreakpoint[], width?: number): number {\n const matcher = width\n ? (item: ColumnsBreakpoint) => (item.min === undefined || item.min <= width) && (item.max === undefined || item.max >= width)\n : (item: ColumnsBreakpoint) => this.mediaObserver.isActive(item.query);\n\n for (const current of source) {\n if (matcher(current)) {\n return current.columns;\n }\n }\n\n return 1;\n }\n\n private parseGridBreakpoints(source: GridBreakpoint): ColumnsBreakpoint[] {\n return entries(this.mediaObserver.breakpoints)\n .filter((point) => point[0] in source)\n .map(([key, value]) => this.parseColumnsBreakpoint(key, value, source))\n .filter((point) => point.min || point.max)\n .sort((first, second) => second.columns - first.columns);\n }\n\n private parseColumnsBreakpoint(name: string, query: string, source: GridBreakpoint): ColumnsBreakpoint {\n const regex = /(min|max)-width\\:\\s*(\\d+)px/gi;\n let match: RegExpExecArray | null;\n\n const result: ColumnsBreakpoint = { query: name, columns: source[name] };\n\n while ((match = regex.exec(query))) {\n //@ts-ignore using match[1] as key index for result doesn't work, add expect error to bypass compiler issues.\n result[match[1]] = parseInt(match[2]);\n }\n\n return result;\n }\n}\n","/* eslint-disable no-param-reassign */\n\n/* eslint-disable @typescript-eslint/no-unsafe-argument */\n\n/* eslint-disable no-prototype-builtins */\n\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n\n/* eslint-disable @typescript-eslint/no-unsafe-call */\n\n/* eslint-disable @typescript-eslint/no-unsafe-return */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { ɵComponentType as ComponentType, ɵNG_COMP_DEF as NG_COMP_DEF } from '@angular/core';\n\nimport { capitalize } from 'lodash-es';\n\nconst WIREDUP: unique symbol = Symbol('__hooks__wiredup__');\nconst REGISTERED_WIREDUP: unique symbol = Symbol('__hooks__wiredup__registered__');\n\nfunction markWiredUp(type: any): void {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-param-reassign\n type.prototype[WIREDUP] = true;\n}\n\nfunction isComponent(type: any): type is ComponentType {\n return type.hasOwnProperty(NG_COMP_DEF);\n}\n\nfunction wireUpComponent(type: ComponentType): void {\n type.prototype.ngOnInit = wireUp(type.prototype.ngOnInit, 'init');\n markWiredUp(type);\n}\n\nfunction wireUp(hook: Function | null | undefined, target: 'init'): Function {\n const pre = `preNgOn${capitalize(target)}`;\n const post = `postNgOn${capitalize(target)}`;\n\n return function (this: any): void {\n this[pre]?.();\n hook?.call(this);\n this[post]?.();\n };\n}\n\nexport function HooksWireup(): (target: any) => void {\n return function (target: any): void {\n if (!target.prototype[REGISTERED_WIREDUP]) {\n throw new Error(\n `Decorator @HooksWireup used in the class ${target.name} should only be used when class extends a hook implementation like OnRouteResolve.`,\n );\n }\n\n if (isComponent(target)) {\n wireUpComponent(target);\n } else {\n throw new Error(\n `Cannot wireup class ${target.name} as it is not decorated with @Component. Also check the order of the directives, as @HooksWireup should come prior to the other ones.`,\n );\n }\n };\n}\n\nexport abstract class Hook {\n get [REGISTERED_WIREDUP](): boolean {\n return true;\n }\n\n constructor() {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const prototype = Object.getPrototypeOf(this);\n\n if (!prototype[WIREDUP]) {\n throw new Error(`To make the custom hooks work correctly, the class ${prototype.constructor.name} should be decorated with @HooksWireup`);\n }\n }\n}\n","import { DestroyRef, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { Hook } from './hooks-wireup';\n\nexport abstract class OnRouteResolve extends Hook {\n protected readonly destroyRef = inject(DestroyRef);\n\n constructor(protected route: ActivatedRoute) {\n super();\n }\n\n preNgOnInit(): void {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(({ model }) => this.onRouteResolved(model));\n }\n\n abstract onRouteResolved(model: TModel): void;\n}\n","import { Observable, Operator, OperatorFunction, Subscriber } from 'rxjs';\n\nclass BufferWithSizeSubscriber extends Subscriber {\n private buffer: T[] = [];\n\n constructor(\n public override destination: Subscriber,\n private bufferSize: number,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n closingNotifier: Observable,\n ) {\n super(destination);\n closingNotifier.subscribe(() => {\n this.emitAndClearBuffer(this.buffer);\n });\n }\n\n private emitAndClearBuffer(buffer: T[]): void {\n this.destination.next(buffer);\n this.buffer = [];\n }\n\n protected override _next(value: T): void {\n const buffer = this.buffer;\n buffer.push(value);\n\n if (buffer.length === this.bufferSize) {\n this.emitAndClearBuffer(buffer);\n }\n }\n\n protected override _complete(): void {\n const buffer = this.buffer;\n if (buffer.length > 0) {\n this.destination.next(buffer);\n }\n super._complete();\n }\n}\n\nclass BufferWithSizeOperator implements Operator {\n constructor(\n private bufferSize: number,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private closingNotifier: Observable,\n ) {}\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n call(subscriber: Subscriber, source: any): any {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n return source.subscribe(new BufferWithSizeSubscriber(subscriber, this.bufferSize, this.closingNotifier));\n }\n}\n\n/**\n * Combination of buffer() and bufferCount()\n *\n * @param bufferSize Argument of bufferCount()\n * @param closingNotifier Argument of buffer()\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function bufferWithSize(bufferSize: number, closingNotifier: Observable): OperatorFunction {\n return function bufferWithSizeOperatorFunction(source: Observable): Observable {\n return source.lift(new BufferWithSizeOperator(bufferSize, closingNotifier));\n };\n}\n","/* eslint-disable @typescript-eslint/prefer-ts-expect-error */\n/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { StringDictionary } from '@frontend/sports/common/core/utils/extended-types';\n\nexport const isShallowCopy = (prev: T, next: T): boolean => {\n // eslint-disable-next-line eqeqeq\n if (prev === null && next === null) {\n // When both null then objects are equal\n return true;\n }\n\n // eslint-disable-next-line eqeqeq\n if (prev === null || next === null) {\n // When one is null then objects are unequal\n return false;\n }\n\n if (prev === undefined && next === undefined) {\n // When both undefined then objects are equal\n return true;\n }\n\n if (prev === undefined || next === undefined) {\n // When one is undefined then objects are unequal\n return false;\n }\n\n const prevKeys = Object.keys(prev);\n const nextKeys = Object.keys(next);\n if (prevKeys.length !== nextKeys.length) {\n // If number of keys is different: unequal\n return false;\n }\n for (const key of nextKeys) {\n //below ignore was added to get rid of suppressImplicitAnyIndexErrors in our tsconfig, at least it's limited to this usage now.\n //@ts-ignore\n if (prev[key] !== next[key]) {\n // One key is different: unequal\n return false;\n }\n }\n\n return true; // Keys are the same: equal\n};\n\nexport const calcDiff = function (prev: T[], next: T[]): { added: T[]; removed: T[] } {\n if ((!prev && !next) || (prev.length === 0 && next.length === 0)) {\n return { removed: [], added: [] };\n }\n if (!prev || prev.length === 0) {\n return { removed: [], added: [...next] };\n }\n if (!next || next.length === 0) {\n return { removed: [...prev], added: [] };\n }\n\n const removed = new Map(prev.map((item) => [item, true]));\n const added = new Map(next.map((item) => [item, true]));\n for (const [item] of removed) {\n if (added.has(item)) {\n removed.set(item, false);\n }\n }\n for (const [item] of added) {\n if (removed.has(item)) {\n added.set(item, false);\n }\n }\n\n return {\n removed: [...removed.entries()].filter((e) => !!e[1]).map((e) => e[0]),\n added: [...added.entries()].filter((e) => !!e[1]).map((e) => e[0]),\n };\n};\n\nexport const toDictionary = function (\n list: T[] | undefined,\n keySelector: (item: T) => TKey,\n valueSelector: (item: T, index: number) => TValue,\n): StringDictionary {\n const result: StringDictionary = {};\n\n if (!list) {\n return {};\n }\n\n for (let i = 0; i < list.length; i++) {\n const item = list[i];\n const key = keySelector(item);\n\n if (key in result) {\n console.warn('Item with the same key has already been added');\n continue;\n }\n\n result[key] = valueSelector(item, i);\n }\n\n return result;\n};\n","import { InjectionToken, Type } from '@angular/core';\n\nimport { Widget, WidgetType } from '@frontend/sports/types/components/widget';\nimport { Observable } from 'rxjs';\n\nexport interface WidgetSkeletonModel {\n widget?: Widget;\n showSkeleton$: Observable;\n}\nexport interface WidgetSkeletonConfig {\n visible: boolean;\n loaderClass?: string;\n}\n// so we can inject it\nexport class WidgetSkeletonData {\n widget: Widget;\n widgetsBeforeLoaded$: Observable;\n config: (payload: T) => Observable;\n}\nexport interface WidgetSkeletonDefinition {\n config: (payload: T) => Observable;\n type: WidgetType;\n component: Type;\n}\n\nexport const WIDGET_SKELETON = new InjectionToken>('ms-widget-skeleton');\n","import { Component, DestroyRef, Injectable, Injector, Type, effect, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { WidgetConfig } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory } from '@frontend/sports/common/core/feature/logging';\nimport { toDictionary } from '@frontend/sports/common/core/utils/collection';\nimport { bufferWithSize } from '@frontend/sports/common/core/utils/rxjs';\nimport { Widget, WidgetContext, WidgetLocation, WidgetType } from '@frontend/sports/types/components/widget';\nimport { NativeAppService } from '@frontend/vanilla/core';\nimport { omit } from 'lodash-es';\nimport { Subject, Subscription, combineLatest, debounceTime, filter, iif, map, of, take, timeout } from 'rxjs';\n\nimport { WidgetData, WidgetLoaderService } from './widget-loader.service';\nimport { WIDGET_SKELETON, WidgetSkeletonDefinition } from './widget-skeleton.model';\n\nexport interface RegisteredWidget {\n componentType: Type>;\n injector?: Injector;\n}\n\n@Component({\n template: '',\n host: {\n '[class.hidden]': '_hidden()',\n },\n})\nexport abstract class WidgetComponent {\n get hidden() {\n return this._hidden();\n }\n\n set hidden(hidden) {\n this._hidden.set(hidden);\n }\n\n _hidden = signal(!inject(NativeAppService).isTerminal);\n hasData = true;\n private widgetAlreadyReady: boolean;\n private widgetLoaderService = inject(WidgetLoaderService);\n private widgetConfig = signal | undefined>(undefined);\n private parentConfig?: Widget;\n private widgetLoadedSubscription$: Subscription;\n private logger = inject(LoggerFactory).getLogger('WidgetComponent');\n private widgetClientConfig = inject(WidgetConfig);\n\n get parent(): Readonly> | undefined {\n return this.parentConfig;\n }\n\n get config(): Readonly> {\n return this.widgetConfig()!;\n }\n\n protected logEmptyData(): void {\n this.logger.warning(`Empty widget data configured for ${this.config.id}`);\n }\n\n protected readonly destroyRef = inject(DestroyRef);\n\n constructor() {\n effect(() => {\n const config = this.widgetConfig();\n const visible = !this._hidden();\n if (config) {\n this.widgetLoaderService.setWidgetVisible(`${this.config.location}-${this.config.id}`, visible);\n }\n });\n }\n\n onResolve(widget: Widget, parent?: Widget, shouldCallOnData: boolean = true, isWidgetRefresh: boolean = false): void {\n this.widgetConfig.set(omit(widget, 'payload') as Widget);\n\n if (parent) {\n this.parentConfig = omit(parent, 'payload') as Widget;\n }\n\n if (shouldCallOnData) {\n if (this.config.location !== WidgetLocation.Right && !isWidgetRefresh) {\n this.widgetAlreadyReady = false;\n this.subscribeWidgetLoaded();\n }\n\n this.onData?.(widget.payload, isWidgetRefresh);\n if (parent?.payload) {\n this.onParentData?.(parent.payload);\n }\n }\n }\n\n protected onData?(data?: T, isWidgetRefresh?: boolean): void;\n\n protected onParentData?(data: unknown): void;\n\n getContext(): Partial {\n return { widgetId: this.config.id };\n }\n\n protected setWidgetLoaded(): void {\n if (this.widgetAlreadyReady) {\n this.hidden = !this.hasData;\n\n return;\n }\n\n //Adding below check to avoid null config from widget constructor on initial load\n if (this.config) {\n this.widgetLoaderService.setWidgetLoaded(`${this.config.location}-${this.config.id}`);\n }\n }\n\n private subscribeWidgetLoaded(): void {\n this.widgetLoadedSubscription$?.unsubscribe();\n this.widgetLoadedSubscription$ = combineLatest([\n this.widgetLoaderService.widgetLoadedDetails$(this.config.location),\n iif(\n () => [WidgetLocation.Left, WidgetLocation.Center].includes(this.config.location),\n this.widgetLoaderService.allTopLocationWidgetsLoaded$,\n of(true),\n ),\n ])\n .pipe(\n map(([widgetsData, topLocationWidgetsLoaded]) => topLocationWidgetsLoaded && this.checkWidgetReady(widgetsData)),\n filter(Boolean),\n take(1),\n timeout({\n each: this.widgetClientConfig.orderedRenderingTimeout,\n with: () => of(false),\n }),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe((loadedWithoutTimeout) => {\n if (!loadedWithoutTimeout) {\n this.logger.warning(\n `Widget ordered rendering timed out after ${this.widgetClientConfig.orderedRenderingTimeout}ms for ${this.config.id}`,\n );\n }\n this.widgetAlreadyReady = true;\n this.hidden = !this.hasData;\n });\n }\n\n private checkWidgetReady = (widgets: WidgetData[]): boolean => {\n return widgets.findIndex((x) => x.widgetId === this.config.id && x.isLoaded) > -1 && this.checkPreceedingWidgetsReady(widgets);\n };\n\n private checkPreceedingWidgetsReady = (widgets: WidgetData[]): boolean => {\n const currentWidgetOrder = widgets.find((widget) => widget.widgetId === this.config.id)!.order;\n const filteredWidgets = widgets.filter((x) => x.order <= currentWidgetOrder);\n\n return filteredWidgets.every((x) => x.isLoaded);\n };\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetRegistryService {\n // inject all candidates that got registered\n private readonly widgetSkeletonConfig = inject[]>(WIDGET_SKELETON, { optional: true });\n // create a dictionary for faster access\n readonly widgetSkeletons = this.widgetSkeletonConfig\n ? toDictionary(\n this.widgetSkeletonConfig,\n ({ type }) => type.toString(),\n (v) => v,\n )\n : {};\n\n private readonly widgets = new Map();\n private readonly lazyWidgets = new Subject();\n\n private readonly debounceTimer = this.lazyWidgets.pipe(debounceTime(2));\n readonly lazyWidgets$ = this.lazyWidgets.pipe(\n bufferWithSize(10, this.debounceTimer),\n filter((data) => data.length > 0),\n );\n\n register(name: WidgetType, widget: Type>): void {\n this.assertNotAlreadyRegistered(name);\n this.widgets.set(name, { componentType: widget });\n }\n\n registerLazy(name: WidgetType, widget: Type>, injector?: Injector): void {\n this.assertNotAlreadyRegistered(name);\n\n this.widgets.set(name, { componentType: widget, injector });\n this.lazyWidgets.next(name);\n }\n\n get(name: WidgetType): RegisteredWidget | null {\n return this.widgets.get(name) ?? null;\n }\n\n private assertNotAlreadyRegistered(type: WidgetType): void {\n if (this.widgets.has(type)) {\n throw new Error(`Widget with name ${type} is already registered`);\n }\n }\n}\n","import { hasValue } from '@frontend/sports/common/core/utils/extended-types';\nimport { Widget, WidgetType } from '@frontend/sports/types/components/widget';\nimport { ComposableWidgetPayload } from '@frontend/sports/types/components/widget/types';\nimport { flatMap, uniqBy } from 'lodash-es';\n\nexport function widgetsToWidgetTypes(widgets: Widget[] | undefined) {\n let widgetTypes = widgets?.map((widget) => widget.type);\n const composableWidget = widgets?.find((widget) => widget.type === WidgetType.Composable);\n if (composableWidget) {\n const composableWidgetPayload = (composableWidget.payload ?? {}) as ComposableWidgetPayload;\n const childWidgets = uniqBy(\n flatMap(composableWidgetPayload.items, (item) => item.children),\n (child) => child.type,\n ).map((child) => child.type);\n widgetTypes = [...childWidgets, ...widgetTypes!];\n }\n return widgetTypes?.filter(hasValue);\n}\n","import { Injectable } from '@angular/core';\n\nimport { WidgetConfig } from '@frontend/sports/common/client-config-data-access';\nimport { filterSportsEmitLast } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { Widget, WidgetLayoutTemplate, WidgetPage } from '@frontend/sports/types/components/widget';\nimport { Observable, ReplaySubject } from 'rxjs';\nimport { distinctUntilKeyChanged, filter } from 'rxjs/operators';\n\nimport ModuleLoaderService from '../../deferred/module-loader.service';\nimport { AdaptiveLayoutService } from '../../layout/adaptive-layout.service';\nimport { widgetsToWidgetTypes } from './widgets-to-widget-types.util';\n\nexport interface WidgetRightColumnContent {\n widgets: Widget[];\n page?: WidgetPage;\n layoutTemplate?: WidgetLayoutTemplate;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetRightColumnService {\n private widgets = new ReplaySubject(1);\n\n constructor(\n private config: WidgetConfig,\n private moduleLoader: ModuleLoaderService,\n layout: AdaptiveLayoutService,\n ) {\n layout.stateChange$\n .pipe(\n distinctUntilKeyChanged('hasRightColumn'),\n filterSportsEmitLast(),\n filter((current) => current.hasRightColumn),\n )\n .subscribe(() => this.resetContent());\n this.widgets.pipe(filter(({ widgets }) => widgets.length > 0)).subscribe(({ widgets }) => {\n const widgetTypes = widgetsToWidgetTypes(widgets);\n this.moduleLoader.lazyLoadFeatures(widgetTypes!);\n });\n this.setContent([]);\n }\n\n setContent(widgets: Widget[], page?: WidgetPage, layoutTemplate?: WidgetLayoutTemplate): void {\n if (!widgets.length) {\n widgets = this.config.defaultRightColumn;\n }\n\n this.widgets.next({\n widgets,\n page,\n layoutTemplate,\n });\n }\n\n resetContent(): void {\n this.setContent([]);\n }\n\n asObservable(): Observable {\n return this.widgets.asObservable();\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { TrackData, TrackUpdate, TrackingService } from '@frontend/sports/tracking/feature';\n/* eslint-disable no-restricted-imports */\nimport { TrackingService as VanillaTrackingService } from '@frontend/vanilla/core';\nimport { isEmpty } from 'lodash-es';\n\nimport { PickSourceProvider } from '../../option-pick/pick-source.provider';\n\n@Injectable()\nexport class ModularTrackingService extends TrackingService {\n constructor(\n private pickSourceProvider: PickSourceProvider,\n vanillaTracking: VanillaTrackingService,\n ) {\n super(vanillaTracking);\n }\n\n override track(event: string, data?: Partial, options?: any): void {\n super.track(event, this.updateData(data), options);\n }\n\n override update(data: Partial): void {\n super.update(this.updateData(data));\n }\n\n override triggerEventWithCleaning(event?: string, data?: Partial, options?: any): void {\n super.triggerEventWithCleaning(event, this.updateData(data), options);\n }\n\n private updateData(trackingObject: T): T {\n const baseTracking = this.pickSourceProvider.getTracking();\n\n return isEmpty(baseTracking)\n ? trackingObject\n : {\n ...trackingObject,\n ...baseTracking,\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Widget, WidgetContext } from '@frontend/sports/types/components/widget';\nimport { Observable } from 'rxjs';\n\nimport { WidgetContextService } from './widget-context.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetContextRefreshProcessor {\n constructor(private context: WidgetContextService) {}\n\n refresh(contexts: Partial[]): Observable[] | undefined> {\n return this.context.getWidgetBatch(contexts);\n }\n}\n","import { Injectable } from '@angular/core';\nimport { ActivationEnd } from '@angular/router';\n\nimport { CDNCachingConfig, WidgetConfig } from '@frontend/sports/common/client-config-data-access';\nimport { RouteTag } from '@frontend/sports/common/core/data-access/route';\nimport { ofType } from '@frontend/sports/common/core/utils/extended-types';\nimport { RouterEventsService } from '@frontend/sports/common/core/utils/router-events';\nimport { filterSports } from '@frontend/sports/host-app/sports-product/feature/utils';\nimport { WidgetPage } from '@frontend/sports/types/components/widget';\nimport { EMPTY, Observable, Subscription, interval, map, noop } from 'rxjs';\n\nimport { WidgetContextRefreshProcessor } from '../common/widget-context-refresh-processor';\nimport { WidgetComponent } from './widget-registry.service';\n\nexport abstract class WidgetRefreshTrigger {\n constructor(private config: WidgetConfig) {}\n\n asObservable(): Observable {\n const time = this.config.pageRefreshInterval\n .filter(({ page }) => page === this.getPage())\n .map((page) => page.interval)\n .pop();\n\n if (time) {\n return interval(time * 1000).pipe(map(noop));\n }\n\n return EMPTY;\n }\n\n abstract getPage(): WidgetPage;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class WidgetRefreshService {\n private components = new Map, undefined>();\n private triggers = new Map();\n\n private refresh?: Subscription;\n\n constructor(\n router: RouterEventsService,\n private processor: WidgetContextRefreshProcessor,\n private cdnCacheConfig: CDNCachingConfig,\n ) {\n router.currentActivationEnd.pipe(filterSports(), ofType(ActivationEnd)).subscribe((route) => this.registerRefresh(route.snapshot.data.tag));\n }\n\n deregisterComponent(widgets: WidgetComponent[]): void {\n for (const widget of widgets) {\n this.components.delete(widget);\n }\n }\n\n registerComponent(widgets: WidgetComponent[]): void {\n for (const widget of widgets) {\n this.components.set(widget, undefined);\n }\n }\n\n registerTrigger(tag: RouteTag, trigger: WidgetRefreshTrigger): void {\n this.triggers.set(tag, trigger);\n }\n\n private registerRefresh(tag: RouteTag): void {\n const trigger = this.triggers.get(tag);\n\n this.refresh?.unsubscribe();\n this.refresh = trigger?.asObservable().subscribe(() => {\n const components = [...this.components.keys()].filter((component) => component.config.refreshable);\n const contexts = components\n .filter((component) => !this.cdnCacheConfig.widgets.includes(component.config.type))\n .map((component) => component.getContext());\n\n if (contexts.length) {\n this.processor.refresh(contexts).subscribe((widgets) => {\n for (const widget of widgets!) {\n const component = components.find((current) => current.config.id === widget.id);\n\n if (component) {\n component.onResolve(widget, undefined, true, true);\n }\n }\n });\n }\n components\n .filter((component) => this.cdnCacheConfig.widgets.includes(component.config.type) && component.config.refreshable)\n .forEach((component) => component.onResolve(component.config, undefined, true, true));\n });\n }\n}\n","import { Injectable, Injector, ViewContainerRef, inject } from '@angular/core';\n\nimport { Widget, WidgetLocation } from '@frontend/sports/types/components/widget';\nimport { Observable, Subject, combineLatest, filter, iif, map, of, shareReplay, take, takeUntil } from 'rxjs';\n\nimport { WidgetLoaderService } from './widget-loader.service';\nimport { WidgetRegistryService } from './widget-registry.service';\nimport { WidgetSkeletonData } from './widget-skeleton.model';\n\n@Injectable({ providedIn: 'root' })\nexport class WidgetSkeletonRenderer {\n private readonly componentRegistry = inject(WidgetRegistryService);\n private readonly widgetLoaderService = inject(WidgetLoaderService);\n\n private readonly widgetSkeletons = new Map void>();\n\n maybeRenderSkeleton({\n widget,\n index,\n injector: parentInjector,\n allWidgets,\n containerRef,\n }: {\n widget: Widget;\n index: number;\n injector: Injector;\n allWidgets: Widget[];\n containerRef: ViewContainerRef;\n }): void {\n const skeletonConfig = this.componentRegistry.widgetSkeletons[widget.type];\n // if we have a skeleton configured and the slot is empty\n if (skeletonConfig && !this.widgetSkeletons.get(widget.id)) {\n const widgetsBefore = allWidgets.filter((w) => w.location === widget.location && w.order < widget.order);\n const topWidgetsLoaded$ = iif(\n () => widget.location === WidgetLocation.Left || widget.location === WidgetLocation.Center,\n this.widgetLoaderService.allTopLocationWidgetsLoaded$.pipe(filter(Boolean)),\n of(true),\n );\n let widgetsBeforeLoaded$: Observable = topWidgetsLoaded$;\n if (widgetsBefore.length > 0) {\n widgetsBeforeLoaded$ = combineLatest([\n this.widgetLoaderService.widgetLoadedDetails$(widget.location).pipe(\n map((widgetDetails) => {\n const sortedOrder = widgetDetails.find(({ widgetId }) => widgetId === widget.id)!.order;\n\n return widgetDetails.filter((widgetData) => widgetData.order < sortedOrder).every((widgetData) => widgetData.isLoaded);\n }),\n ),\n topWidgetsLoaded$,\n ]).pipe(\n map(([widgetsBeforeLoaded, topWidgetsLoaded]) => widgetsBeforeLoaded && topWidgetsLoaded),\n shareReplay({ refCount: true, bufferSize: 1 }),\n );\n }\n const dispose = new Subject();\n const injector: Injector = Injector.create({\n parent: parentInjector,\n providers: [\n {\n provide: WidgetSkeletonData,\n useValue: {\n widget,\n widgetsBeforeLoaded$,\n config: skeletonConfig.config,\n },\n },\n ],\n });\n const loader = containerRef.createComponent(skeletonConfig.component, { index, injector });\n loader.changeDetectorRef.detectChanges();\n loader.instance.showSkeleton$.pipe(filter(Boolean), take(1), takeUntil(dispose)).subscribe(() => {\n this.widgetLoaderService.setWidgetLoaded(`${widget.location}-${widget.id}`);\n });\n this.widgetSkeletons.set(widget.id, () => {\n dispose.next();\n containerRef.remove(containerRef.indexOf(loader.hostView));\n this.widgetSkeletons.delete(widget.id);\n });\n }\n }\n\n removeSkeleton(id: string) {\n this.widgetSkeletons.get(id)?.();\n }\n}\n","import {\n Component,\n ComponentRef,\n ElementRef,\n HostBinding,\n Injectable,\n Injector,\n Input,\n OnChanges,\n OnDestroy,\n Optional,\n SkipSelf,\n ViewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { LoggerFactory, SportsRemoteLogger } from '@frontend/sports/common/core/feature/logging';\nimport { GridBreakpointSize, GridLayout } from '@frontend/sports/grid/core/feature/model';\nimport { Modal } from '@frontend/sports/modal/feature';\nimport { TrackingService } from '@frontend/sports/tracking/feature';\nimport { Widget, WidgetLayoutTemplate, WidgetLocation, WidgetPage } from '@frontend/sports/types/components/widget';\nimport { isNumber, sortBy } from 'lodash-es';\nimport { EMPTY, Observable } from 'rxjs';\n\nimport { GridBreakpointOptions, GridBreakpointService } from '../../grid/grid-breakpoint.service';\nimport { PickSourceProvider } from '../../option-pick/pick-source.provider';\nimport { ModularConfigAccessorService } from './modular-config-accessor.service';\nimport { ModularPickSourceService } from './modular-pick-source.service';\nimport { ModularTrackingService } from './modular-tracking.service';\nimport { WidgetData, WidgetLoaderService } from './widget-loader.service';\nimport { WidgetRefreshService } from './widget-refresh.service';\nimport { WidgetComponent, WidgetRegistryService } from './widget-registry.service';\nimport { WidgetSkeletonRenderer } from './widget-skeleton-renderer.service';\n\nabstract class WidgetRef extends ComponentRef> {}\n\ninterface ReusableWidgetRef {\n config: Widget;\n configIndex?: number;\n component?: WidgetRef;\n componentIndex?: number;\n}\n\ninterface ReusedWidgetRef extends ReusableWidgetRef {\n configIndex: number;\n component: WidgetRef;\n componentIndex: number;\n}\n\n@Injectable()\nexport class WidgetSlotGridBreakpointService implements Pick {\n private calculateGridWidth: (containerWidth: number) => number;\n\n constructor(@SkipSelf() @Optional() private gridBreakpoint?: GridBreakpointService) {\n this.forRatio(1);\n }\n\n forGrid(element: ElementRef, layout: GridLayout, options?: GridBreakpointOptions): Observable {\n if (!this.gridBreakpoint) {\n return EMPTY;\n }\n\n return this.gridBreakpoint.forGrid(element, layout, { ...options, calculateGridWidth: this.calculateGridWidth });\n }\n\n forRatio(ratio: number): void {\n if (ratio === 1) {\n this.calculateGridWidth = (containerWidth) => containerWidth;\n } else {\n this.calculateGridWidth = (containerWidth) => (containerWidth - 16) * ratio;\n }\n }\n}\n\n@Component({\n selector: 'ms-widget-slot',\n template: '',\n providers: [\n WidgetSlotGridBreakpointService,\n {\n provide: GridBreakpointService,\n useExisting: WidgetSlotGridBreakpointService,\n },\n ],\n})\nexport class WidgetSlotComponent implements OnChanges, OnDestroy {\n @Input() widget: Widget[];\n @Input() parent?: Widget;\n @Input() page?: WidgetPage;\n @Input() ratio?: number;\n @Input() layoutTemplate?: WidgetLayoutTemplate;\n\n @HostBinding('class') className = 'widget-slot';\n @ViewChild('container', { read: ViewContainerRef, static: true }) containerRef: ViewContainerRef;\n\n private components: WidgetRef[] = [];\n\n private logger: SportsRemoteLogger;\n\n constructor(\n private componentRefresh: WidgetRefreshService,\n private componentRegistry: WidgetRegistryService,\n private gridBreakpoint: WidgetSlotGridBreakpointService,\n private injector: Injector,\n private widgetLoaderService: WidgetLoaderService,\n private widgetSkeletonRenderer: WidgetSkeletonRenderer,\n loggerFactory: LoggerFactory,\n ) {\n this.logger = loggerFactory.getLogger('WidgetSlotComponent');\n\n // onChanges is triggered 2 times on page load because of lazy widgets -> careful with lazy loading data in onResolve\n componentRegistry.lazyWidgets$.pipe(takeUntilDestroyed()).subscribe(() => this.createOrUpdateWidgetComponent(true));\n }\n\n ngOnChanges(): void {\n this.createOrUpdateWidgetComponent();\n }\n\n createOrUpdateWidgetComponent(isLazy: boolean = false) {\n this.gridBreakpoint.forRatio(this.ratio ?? 1);\n\n const reused: WidgetRef[] = [];\n\n const ordered: ReusableWidgetRef[] = sortBy(this.widget, (config) => config.order).map((config, index) => {\n const existing = this.components.findIndex((component) => component.instance.config.type === config.type && !reused.includes(component));\n\n if (existing === -1) {\n return { config };\n }\n\n reused.push(this.components[existing]);\n\n return {\n config,\n configIndex: index,\n component: this.components[existing],\n componentIndex: existing,\n };\n });\n\n this.normalizeReused(ordered, 'configIndex');\n this.normalizeReused(ordered, 'componentIndex');\n\n this.destroyUnused(reused);\n if (!isLazy) {\n ordered.forEach(({ config }, index) => {\n this.widgetLoaderService.setSlotWidgets(`${config.location}-${config.id}`, {\n isLoaded: config.location === WidgetLocation.Right,\n order: index,\n slot: config.location,\n widgetId: config.id,\n parentWidgetId: this.parent?.id,\n } as WidgetData);\n });\n }\n\n ordered\n .filter((widget) => this.componentRegistry.get(widget.config.type) || this.componentRegistry.widgetSkeletons[widget.config.type])\n .forEach(({ config, component }, index) => {\n try {\n if (component) {\n this.updateComponent(config, component, !isLazy);\n\n this.orderReused(ordered, index);\n } else {\n this.createComponent(config, index);\n }\n } catch (error: any) {\n this.log(error);\n }\n });\n\n this.componentRefresh.registerComponent(this.components.map((component) => component.instance));\n }\n\n ngOnDestroy(): void {\n this.componentRefresh.deregisterComponent(this.components.map((component) => component.instance));\n }\n\n private createComponent(config: Widget, index: number): void {\n const registeredComponent = this.componentRegistry.get(config.type);\n if (!registeredComponent) {\n if (config.payload) {\n this.widgetSkeletonRenderer.maybeRenderSkeleton({\n widget: config,\n allWidgets: this.widget,\n index,\n containerRef: this.containerRef,\n injector: this.injector,\n });\n }\n\n return;\n }\n\n const injector: Injector = Injector.create({\n providers: [\n { provide: ModularConfigAccessorService, useClass: ModularConfigAccessorService },\n { provide: PickSourceProvider, useClass: ModularPickSourceService },\n { provide: TrackingService, useClass: ModularTrackingService },\n { provide: Modal, useClass: Modal },\n ],\n parent: registeredComponent.injector ?? this.injector,\n });\n\n this.widgetSkeletonRenderer.removeSkeleton(config.id);\n const component = this.containerRef.createComponent(registeredComponent.componentType, { index, injector });\n this.updateComponent(config, component, true);\n }\n\n private isReused(reused: ReusableWidgetRef): reused is ReusedWidgetRef {\n return reused.component !== undefined;\n }\n\n private updateComponent(config: Widget, widgetComponent: ComponentRef>, shouldCallOnData: boolean): void {\n try {\n const widget = { ...config, order: this.components.length };\n const accessor = widgetComponent.injector.get(ModularConfigAccessorService);\n\n accessor.setWidget(widget);\n accessor.setPage(this.page);\n accessor.setLayoutTemplate(this.layoutTemplate);\n accessor.setParentWidget(this.parent);\n if (shouldCallOnData) {\n widgetComponent.instance.hidden = config.location !== WidgetLocation.Right;\n }\n widgetComponent.instance.onResolve(widget, this.parent, shouldCallOnData);\n } catch (error: any) {\n this.log(error);\n widgetComponent.instance.hasData = false;\n this.widgetLoaderService.setWidgetLoaded(`${config.location}-${config.id}`);\n }\n // this is important: update the widgets bindings\n widgetComponent.hostView.detectChanges();\n this.components.push(widgetComponent);\n }\n\n private orderReused(reused: ReusableWidgetRef[], index: number): void {\n const currentWidget = reused[index];\n\n if (!this.isReused(currentWidget) || currentWidget.configIndex === currentWidget.componentIndex) {\n return;\n }\n\n this.containerRef.move(currentWidget.component.hostView, index);\n currentWidget.componentIndex = currentWidget.configIndex;\n\n reused\n .filter(\n (current) =>\n this.isReused(current) &&\n current.configIndex > currentWidget.configIndex &&\n current.componentIndex < currentWidget.componentIndex,\n )\n .forEach((current) => {\n if (this.isReused(current)) {\n current.componentIndex++;\n }\n });\n }\n\n private destroyUnused(reused: ComponentRef>[]): void {\n const unusedWidget = this.components.filter((component) => !reused.includes(component));\n\n this.componentRefresh.deregisterComponent(unusedWidget.map((current) => current.instance));\n this.components = [];\n\n unusedWidget.forEach((component) => component.destroy());\n }\n\n private normalizeReused(source: ReusableWidgetRef[], target: 'configIndex' | 'componentIndex'): void {\n sortBy(\n source.filter((config) => isNumber(config[target])),\n (config) => config[target],\n ).forEach((config, index) => {\n config[target] = index;\n });\n }\n\n private log(error: Error): void {\n console.error(error);\n this.logger.error(error);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { hasValue } from '@frontend/sports/common/core/utils/extended-types';\nimport { NOT_APPLICABLE, TrackingService, trackingConstants } from '@frontend/sports/tracking/feature';\nimport { Widget, WidgetLayoutResponse, WidgetType } from '@frontend/sports/types/components/widget';\nimport { LinkListPayload } from '@frontend/sports/types/components/widget/types';\n\n@Injectable({ providedIn: 'root' })\nexport class WidgetTrackingService {\n constructor(private trackingService: TrackingService) {}\n\n trackWidgetsLoaded({ widgets, template, page }: WidgetLayoutResponse): void {\n const linkListWidget = widgets?.find((x) => x.type === WidgetType.LinkList) as Widget | undefined;\n if (linkListWidget?.payload) {\n for (let x = 0; x < linkListWidget?.payload?.links!.length; x++) {\n widgets?.push({\n type: linkListWidget.type,\n location: linkListWidget.location,\n id: linkListWidget.id,\n order: linkListWidget.order,\n refreshable: linkListWidget.refreshable,\n payload: [],\n templateName: linkListWidget.templateName,\n trackingData: linkListWidget.trackingData,\n visible: linkListWidget.visible,\n sitecoreId: linkListWidget?.payload.links?.[x].sitecoreId ?? '',\n userSegmentGroups: linkListWidget?.payload.links?.[x].userSegmentGroups ?? '',\n });\n }\n }\n\n const widgetTrackingData = widgets?.map((w) => {\n return {\n ...w.trackingData,\n [trackingConstants.COMPONENT_MODULE_NAME]: w.type,\n [trackingConstants.COMPONENT_MODULE_POSITION]: `${w.location}|${w.order}`,\n [trackingConstants.COMPONENT_PAGE_LAYOUT]: [template.folder, page, template.name].filter(hasValue).join('/'),\n [trackingConstants.COMPONENT_MODULE_CUSTOM_NAME]: w.templateName,\n [trackingConstants.COMPONENT_MODULE_SOURCE]: 'standard module',\n [trackingConstants.COMPONENT_SITECORE_ID]: w.sitecoreId,\n [trackingConstants.COMPONENT_USER_SEGMENTS]: w.userSegmentGroups || NOT_APPLICABLE,\n };\n });\n\n this.trackingService.track(trackingConstants.SHOW_MODULE, {\n [trackingConstants.SHOW_MODULE]: widgetTrackingData,\n });\n }\n}\n","import { WidgetPage } from '@frontend/sports/types/components/widget';\n\nexport abstract class WidgetLayoutHook {\n constructor(readonly target: WidgetPage) {}\n\n abstract onResolve(): void;\n abstract onDestroy(): void;\n}\n","\n\n\n","import { Component, ElementRef, HostBinding, Inject, OnDestroy, OnInit, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { AutomationService } from '@frontend/sports/automation/core-feature';\nimport { ScrollingSizeConfig } from '@frontend/sports/common/client-config-data-access';\nimport { HooksWireup, OnRouteResolve } from '@frontend/sports/common/core/utils/hooks';\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { Widget, WidgetLayoutResponse, WidgetLayoutTemplate, WidgetLocation, WidgetPage } from '@frontend/sports/types/components/widget';\nimport { MediaQueryService } from '@frontend/vanilla/core';\nimport { kebabCase } from 'lodash-es';\nimport { Subscription } from 'rxjs';\n\nimport { HideHeaderService } from '../../common/hide-header.service';\nimport ModuleLoaderService from '../../deferred/module-loader.service';\nimport { WidgetLayoutHook } from './widget-layout-hook';\nimport { WidgetLoaderService } from './widget-loader.service';\nimport { WidgetPageService } from './widget-page.service';\nimport { WidgetRightColumnService } from './widget-right-column.service';\nimport { WidgetTrackingService } from './widget-tracking.service';\nimport { MEDIUM_LAYOUT_BREAKPOINT, forSlot } from './widget.constants';\nimport { widgetsToWidgetTypes } from './widgets-to-widget-types.util';\n\n@HooksWireup()\n@Component({\n selector: 'ms-widget-layout',\n templateUrl: './widget-layout.component.html',\n})\nexport class WidgetLayoutComponent extends OnRouteResolve implements OnDestroy, OnInit {\n @HostBinding('class') className: string;\n\n page: WidgetPage;\n layoutTemplate: WidgetLayoutTemplate;\n\n top: Widget[] = [];\n left: Widget[] = [];\n center: Widget[] = [];\n\n leftVisible = true;\n private scrollSubscription?: Subscription;\n private widgetPageService = inject(WidgetPageService);\n\n constructor(\n route: ActivatedRoute,\n private media: MediaQueryService,\n private column: WidgetRightColumnService,\n private elementRef: ElementRef,\n private automationService: AutomationService,\n private widgetTrackingService: WidgetTrackingService,\n private moduleLoader: ModuleLoaderService,\n private scrollContainer: ScrollContainerService,\n private hideHeaderService: HideHeaderService,\n private scrollingSizeConfig: ScrollingSizeConfig,\n private widgetLoaderService: WidgetLoaderService,\n @Inject(WidgetLayoutHook) private hooks: WidgetLayoutHook[],\n ) {\n super(route);\n\n media\n .observe()\n .pipe(takeUntilDestroyed())\n .subscribe(() => this.setLeftVisibility());\n\n this.setClass();\n }\n\n ngOnInit(): void {\n if (this.scrollingSizeConfig.hideHeaderOnScroll.includes(this.page)) {\n this.scrollSubscription = this.scrollContainer.scroll.subscribe(() => {\n this.hideHeaderService.onScroll();\n });\n }\n }\n\n ngOnDestroy(): void {\n this.scrollSubscription?.unsubscribe();\n this.widgetLoaderService.cleanWidgetsLoaded();\n this.column.resetContent();\n this.destroyHooks();\n this.widgetPageService.reset();\n this.hideHeaderService.removeHideHeader();\n }\n\n onRouteResolved(model: WidgetLayoutResponse): void {\n if (this.page !== model.page) {\n this.destroyHooks();\n }\n\n this.page = model.page;\n\n this.layoutTemplate = model.template;\n this.resolveHooks();\n this.widgetLoaderService.cleanWidgetsLoaded();\n\n //on demand lazy load widgets using vanilla event strategy approach.\n const widgetTypes = widgetsToWidgetTypes(model.widgets);\n this.moduleLoader.lazyLoadFeatures(widgetTypes!);\n\n this.top = forSlot(model, WidgetLocation.Top);\n this.left = forSlot(model, WidgetLocation.Left);\n this.center = forSlot(model, WidgetLocation.Center);\n\n this.widgetPageService.setActivePage({\n page: this.page,\n top: this.top,\n left: this.left,\n center: this.center,\n });\n\n this.column.setContent(forSlot(model, WidgetLocation.Right), this.page, this.layoutTemplate);\n this.automationService.addAttr(this.elementRef, { widgetPage: model.page, widgetPageId: model.id });\n\n this.setLeftVisibility();\n this.setClass(model.page);\n this.widgetTrackingService.trackWidgetsLoaded(model);\n }\n\n private setLeftVisibility(): void {\n this.leftVisible = this.media.isActive(MEDIUM_LAYOUT_BREAKPOINT) && this.left.length > 0;\n }\n\n private setClass(page?: string): void {\n this.className = ['widget-layout', 'row', kebabCase(page)].filter((current) => current).join(' ');\n }\n\n private resolveHooks(): void {\n this.getHooks().forEach((hook) => hook.onResolve());\n }\n\n private destroyHooks(): void {\n this.getHooks().forEach((hook) => hook.onDestroy());\n }\n\n private getHooks(): WidgetLayoutHook[] {\n return this.hooks.filter((hook) => hook.target === this.page);\n }\n}\n","import { ElementRef } from '@angular/core';\n\nexport class ElementProvider {\n constructor(private elementRef: ElementRef) {}\n\n get element(): ElementRef {\n return this.elementRef;\n }\n}\n","import { Directive, ElementRef } from '@angular/core';\n\nimport { ElementProvider } from '@frontend/sports/common/core/utils/element-provider';\n\nimport { MODAL_DIALOG_CONTAINER } from './modal-dialog.model';\n\n@Directive({\n selector: '[msModalDialogContainer]',\n providers: [\n { provide: MODAL_DIALOG_CONTAINER, deps: [ElementRef], useFactory: (element: ElementRef) => new ElementProvider(element) },\n ],\n standalone: true,\n})\nexport class ModalDialogContainerDirective {}\n","\n","import { Component, ElementRef, OnInit } from '@angular/core';\n\nimport { ScrollContainerService } from '@frontend/sports/common/core/utils/scroll-container';\nimport { Observable } from 'rxjs';\n\nimport { ColumnLayoutService } from '../../layout/column-layout.service';\nimport { LayoutColumn } from '../../layout/layout.model';\nimport { WidgetRightColumnContent, WidgetRightColumnService } from './widget-right-column.service';\n\n@Component({\n selector: 'ms-widget-column',\n templateUrl: './widget-column.component.html',\n providers: [ScrollContainerService],\n})\nexport class WidgetColumnComponent implements OnInit {\n content$: Observable;\n\n constructor(\n service: WidgetRightColumnService,\n private scrollContainer: ScrollContainerService,\n private elementRef: ElementRef,\n private columnLayout: ColumnLayoutService,\n ) {\n this.content$ = service.asObservable();\n }\n\n ngOnInit(): void {\n this.columnLayout.register({\n component: WidgetColumnComponent,\n location: LayoutColumn.Right,\n });\n\n this.scrollContainer.setTarget(this.elementRef.nativeElement);\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { ModalDialogContainerDirective } from '@frontend/sports/modal/feature';\n\nimport { WidgetColumnComponent } from './widget-column.component';\nimport { WidgetLayoutComponent } from './widget-layout.component';\nimport { WidgetSlotComponent } from './widget-slot.component';\n\n@NgModule({\n imports: [CommonModule, ModalDialogContainerDirective],\n declarations: [WidgetLayoutComponent, WidgetSlotComponent, WidgetColumnComponent],\n exports: [WidgetLayoutComponent, WidgetSlotComponent, WidgetColumnComponent],\n})\nexport class WidgetCoreModule {}\n","import { EventEmitter, Injectable, Type } from '@angular/core';\n\n//eslint-disable-next-line no-restricted-imports\nimport { SportsUserSettings } from '@frontend/sports/common/client-config-data-access';\nimport { LoggerFactory } from '@frontend/sports/common/core/feature/logging';\nimport { BetslipDigitalModule } from 'packages/sports/common/betslip/digital/betslip-digital.module';\n\nimport { ComponentLoaderService, ComponentProxy } from '../deferred/component-loader.service';\nimport ModuleLoaderService from '../deferred/module-loader.service';\n\nexport interface RightColumnBinding {\n inColumn: boolean;\n}\n\nexport interface EditMyBetAddPicksContainerMobileComponentBinding {\n picksContainerBottomOffset: EventEmitter;\n}\n\nexport interface BetslipBarComponentBindings {\n barClick: EventEmitter;\n myBetsClick: EventEmitter;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class BetslipDigitalComponentLoaderService extends ComponentLoaderService {\n moduleName = 'BetslipDigitalModule';\n\n constructor(\n moduleLoader: ModuleLoaderService,\n loggerFactory: LoggerFactory,\n private sportsUserSettings: SportsUserSettings,\n ) {\n super(moduleLoader, loggerFactory);\n }\n\n getBetslipComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.betslipComponent, undefined, this.sportsUserSettings);\n }\n\n getEditBetComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.editBetComponent, undefined, this.sportsUserSettings);\n }\n\n getQuickBetComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.quickBetContainerComponent, undefined, this.sportsUserSettings);\n }\n\n getEditBetAddPicksContainerMobileComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.editBetAddPicksContainerMobileComponent, undefined, this.sportsUserSettings);\n }\n\n getModule(): Promise> {\n return import(/* webpackChunkName: \"betslip-digital\" */ 'packages/sports/common/betslip/digital/betslip-digital.module').then(\n (m) => m.BetslipDigitalModule,\n );\n }\n\n getRewardTokensComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.rewardTokensBannerComponent, undefined, this.sportsUserSettings);\n }\n\n getBetslipReturnsMessageComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.betslipReturnsMessagesComponent, undefined, this.sportsUserSettings);\n }\n\n getBetslipBarComponent(): ComponentProxy {\n return this.getComponentProxy((module) => module.betslipBarComponent, undefined, this.sportsUserSettings);\n }\n}\n","/* eslint-disable @typescript-eslint/no-unused-expressions */\n\n/* eslint-disable no-param-reassign */\nimport { Injectable } from '@angular/core';\n\nimport { EventModel, EventOptionGroup } from '@frontend/sports/betting-offer/feature/model';\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedDeleteFactory {\n deleteOptionGroup(event: EventModel, optionGroupId: number): void {\n const groupsWithSubgroup =\n event.marketsDictionary &&\n event.marketsDictionary[optionGroupId] &&\n event.marketsDictionary[optionGroupId].filter((group) => group.detailedGrouping && group.detailedGrouping.marketGroup !== undefined);\n\n groupsWithSubgroup &&\n groupsWithSubgroup.forEach((group) => {\n this.deleteOptions(group, optionGroupId);\n if (group.groupedMarkets && !group.groupedMarkets.length) {\n delete group.groupedMarkets;\n }\n });\n\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete event.marketsDictionary[optionGroupId];\n event.optionGroups = event.optionGroups.filter((m) => m.id !== optionGroupId.toString() && m.id !== '');\n }\n\n private deleteOptions(optionGroup: EventOptionGroup, deleteId: number): void {\n optionGroup.options = optionGroup.options.filter((option) => option.optionGroupId !== deleteId);\n\n if (optionGroup.id === deleteId.toString() && optionGroup.groupedMarkets && optionGroup.groupedMarkets.length && optionGroup.options.length) {\n optionGroup.id = optionGroup.groupedMarkets.shift() || '';\n } else {\n optionGroup.groupedMarkets =\n optionGroup && optionGroup.groupedMarkets && optionGroup.groupedMarkets.filter((id) => id !== deleteId.toString());\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { OfferSource } from '@cds';\nimport { FixtureStage, HybridFixtureStatus, OptionMarket, Participant, ParticipantType } from '@cds/betting-offer';\nimport { ScoreboardSlim } from '@cds/scoreboards/v1';\nimport {\n EnhancedFixtureViewGroupingConfiguration,\n EventModel,\n EventOptionGroup,\n EventParticipantType,\n ExtendedDisplayType,\n SetTab,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { clone, includes } from 'lodash-es';\n\nimport { CommonFixtureFactory } from './common-fixture.factory';\nimport { DetailedFixtureMarket, DetailedFixtureMarketFactory } from './detailed-fixture-market.factory';\nimport { ScoreboardFactory } from './scoreboard-factory.service';\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedUpdateFactory {\n constructor(\n private marketFactory: DetailedFixtureMarketFactory,\n private commonFactory: CommonFixtureFactory,\n private scoreboardFactory: ScoreboardFactory,\n ) {}\n\n update(model: EventModel, openForBetting: boolean, stage: FixtureStage): void {\n this.commonFactory.update(model, openForBetting, stage);\n }\n\n updateScoreboard(fixtureScoreboard: ScoreboardSlim, model: EventModel): void {\n // eslint-disable-next-line no-param-reassign\n model.scoreboard = this.scoreboardFactory.createScoreboard(fixtureScoreboard, model);\n }\n\n updateOptionGroup(\n event: EventModel,\n fixtureMarket: DetailedFixtureMarket,\n fixtureId: string,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n ): void {\n const overrideParticipantNameMarkets =\n (event.offerSource === OfferSource.V2 || event.hybridFixtureData?.status === HybridFixtureStatus.Ok) &&\n this.commonFactory.shouldOverrideParticipantNames(fixtureMarket as OptionMarket, grouping.sportId);\n const participants = this.mapParticipants(event, overrideParticipantNameMarkets);\n const updates = this.marketFactory.create(fixtureMarket, fixtureId, event.online, grouping, participants, event.isPriceBoosted);\n const id = fixtureMarket.id.toString();\n\n // eslint-disable-next-line no-param-reassign\n event.optionGroups = this.updateMarkets(event.optionGroups, updates, event.online, event.isPriceBoosted);\n\n if (overrideParticipantNameMarkets || event.isPriceBoosted) {\n // eslint-disable-next-line no-param-reassign\n event.optionGroups = event.optionGroups.filter((opt) => opt.options.length);\n }\n // eslint-disable-next-line no-param-reassign\n event.marketsDictionary[id] = event.optionGroups.filter((optionGroup) => optionGroup.id === id || includes(optionGroup.groupedMarkets, id));\n }\n\n private mapParticipants(event: EventModel, overrideParticipantNameMarkets: boolean): Participant[] | undefined {\n if (!overrideParticipantNameMarkets) {\n return;\n }\n\n const players = event.players.map(\n (p) =>\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n ({\n id: p.fixtureParticipantId,\n image: clone(p.image),\n name: { short: p.shortName, value: p.name },\n participantId: p.id,\n properties: { team: p.teamId },\n }) as Participant,\n );\n\n if (event.hybridFixtureData) {\n const participants = event.participants\n .filter((x) => x.type === EventParticipantType.Away || x.type === EventParticipantType.Home)\n .map(\n (p) =>\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n ({\n id: p.id,\n image: clone(p.image),\n name: { short: p.shortName, value: p.name },\n participantId: p.id,\n properties: {\n team: p.teamId,\n type: p.type === EventParticipantType.Home ? ParticipantType.HomeTeam : ParticipantType.AwayTeam,\n },\n }) as Participant,\n );\n\n return [...players, ...participants];\n }\n\n return players;\n }\n\n private updateMarkets(\n currentOptionGroups: EventOptionGroup[],\n updateOptionGroups: EventOptionGroup[],\n fixtureOnline: boolean,\n isPriceBoosted?: boolean,\n ): EventOptionGroup[] {\n updateOptionGroups.forEach((update) => {\n const indexes = currentOptionGroups.reduce(\n (acc: number[], curr: EventOptionGroup, i: number) =>\n curr.id === update.id &&\n curr.detailedGrouping.marketSet === update.detailedGrouping.marketSet &&\n (curr.detailedGrouping.marketSet !== SetTab.Other\n ? curr.detailedGrouping.marketOrder === update.detailedGrouping.marketOrder\n : true)\n ? [...acc, i]\n : acc,\n [],\n );\n\n if (!indexes.length) {\n currentOptionGroups.push(update);\n } else {\n indexes.forEach((index) => {\n // eslint-disable-next-line no-param-reassign\n currentOptionGroups[index] = this.marketFactory.update(currentOptionGroups[index], update, fixtureOnline, isPriceBoosted);\n\n if (!currentOptionGroups[index].options.length) {\n currentOptionGroups.splice(index, 1);\n }\n });\n\n this.updateAutomatedPriceBoostedMarket(currentOptionGroups, update, fixtureOnline);\n }\n });\n\n return [...currentOptionGroups];\n }\n\n private updateAutomatedPriceBoostedMarket(\n currentOptionGroups: EventOptionGroup[],\n updateOptionGroup: EventOptionGroup,\n fixtureOnline: boolean,\n ): void {\n const automatedPriceBoostIndex = currentOptionGroups.findIndex(\n (og) => og.id === updateOptionGroup.id && og.detailedGrouping.displayType === ExtendedDisplayType.AutomatedPriceBoost,\n );\n\n if (automatedPriceBoostIndex > -1) {\n const currentDetailedGrouping = clone(currentOptionGroups[automatedPriceBoostIndex].detailedGrouping);\n // eslint-disable-next-line no-param-reassign\n currentOptionGroups[automatedPriceBoostIndex] = this.marketFactory.update(\n currentOptionGroups[automatedPriceBoostIndex],\n updateOptionGroup,\n fixtureOnline,\n true,\n );\n // eslint-disable-next-line no-param-reassign\n currentOptionGroups[automatedPriceBoostIndex].detailedGrouping = currentDetailedGrouping;\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { FixtureStage } from '@cds/betting-offer';\nimport { ScoreboardSlim } from '@cds/scoreboards/v1';\nimport { EnhancedFixtureViewGroupingConfiguration, EventModel } from '@frontend/sports/betting-offer/feature/model';\nimport { EventDetailsConfig } from '@frontend/sports/common/client-config-data-access';\n\nimport { DetailedGroupingFactory } from '../detailed-grouping.factory';\nimport { DetailedCreateFactory, EventDetailsParameters } from './detailed-create.factory';\nimport { DetailedDeleteFactory } from './detailed-delete.factory';\nimport { DetailedFixtureMarket } from './detailed-fixture-market.factory';\nimport { DetailedUpdateFactory } from './detailed-update.factory';\nimport { EventSitemapGroupingFactory } from './event-sitemap-grouping.factory';\n\n@Injectable({ providedIn: 'root' })\nexport class DetailedFixtureFactory {\n constructor(\n private groupingFactory: DetailedGroupingFactory,\n private createFactory: DetailedCreateFactory,\n private updateFactory: DetailedUpdateFactory,\n private deleteFactory: DetailedDeleteFactory,\n private eventDetailsConfig: EventDetailsConfig,\n private eventSitemapGroupingFactory: EventSitemapGroupingFactory,\n ) {}\n\n create(eventParameters: EventDetailsParameters): EventModel {\n return this.createFactory.create(eventParameters);\n }\n\n update(model: EventModel, openForBetting: boolean, stage: FixtureStage): void {\n this.updateFactory.update(model, openForBetting, stage);\n }\n\n updateScoreboard(fixtureScoreboard: ScoreboardSlim, model: EventModel): void {\n this.updateFactory.updateScoreboard(fixtureScoreboard, model);\n }\n\n updateOptionGroup(\n event: EventModel,\n fixtureMarket: DetailedFixtureMarket,\n fixtureId: string,\n grouping: EnhancedFixtureViewGroupingConfiguration,\n ): void {\n this.updateFactory.updateOptionGroup(event, fixtureMarket, fixtureId, grouping);\n this.refreshModel(event, grouping);\n }\n\n deleteOptionGroup(event: EventModel, optionGroupId: number, grouping: EnhancedFixtureViewGroupingConfiguration): void {\n this.deleteFactory.deleteOptionGroup(event, optionGroupId);\n this.refreshModel(event, grouping);\n }\n\n private refreshModel(event: EventModel, grouping: EnhancedFixtureViewGroupingConfiguration): void {\n const betBuilderId = event.getBetBuilderIdByStage();\n\n // eslint-disable-next-line no-param-reassign\n event.optionSets = this.groupingFactory.getOptionSets(event.optionGroups, grouping, event.offerSource, betBuilderId);\n // eslint-disable-next-line no-param-reassign\n event.gamesCount = event.optionGroups.length;\n\n if (this.eventDetailsConfig.enableSitemapNavigation) {\n // eslint-disable-next-line no-param-reassign\n event.sitemapSets = this.eventSitemapGroupingFactory.getSitemapSet(event.optionSets, grouping, event.optionGroups);\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { StatisticsMode } from '@cds/query-objects';\nimport { StatisticsConfig } from '@frontend/sports/common/client-config-data-access';\nimport { SportConstant } from '@frontend/sports/common/core/data-access/constants';\nimport { NumberDictionary } from '@frontend/sports/common/core/utils/extended-types';\nimport { CompetitionRecordsFormat } from '@frontend/sports/types/components/statistics';\nimport { isEmpty, keyBy } from 'lodash-es';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class StatisticsConfigService {\n private readonly teamRankEnabledSportsList: NumberDictionary;\n private readonly pitcherEnabledSportsList: NumberDictionary;\n\n constructor(private statisticsConfig: StatisticsConfig) {\n this.teamRankEnabledSportsList = keyBy(this.statisticsConfig.teamRankEnabledSports, (sport) => sport);\n this.pitcherEnabledSportsList = keyBy(this.statisticsConfig.pitcherEnabledSports, (sport) => sport);\n }\n\n get teamRankEnabledSports(): NumberDictionary {\n return this.teamRankEnabledSportsList;\n }\n\n get pitcherEnabledSports(): NumberDictionary {\n return this.pitcherEnabledSportsList;\n }\n\n get teamRecordsFormat(): { [sport: number]: CompetitionRecordsFormat } {\n return this.statisticsConfig.teamRecordsFormat;\n }\n\n showStats(sportId: SportConstant): boolean {\n return !!(this.teamRankEnabledSports[sportId] || this.pitcherEnabledSports[sportId] || !isEmpty(this.teamRecordsFormat[sportId]));\n }\n\n getStatisticsModesBySportId(sportId?: number): string {\n const statModes: string[] = [];\n let showRank;\n let showPitcher;\n let showRecords;\n if (sportId) {\n showRank = !!this.teamRankEnabledSportsList[sportId];\n showPitcher = !!this.pitcherEnabledSportsList[sportId];\n showRecords = !isEmpty(this.statisticsConfig.teamRecordsFormat[sportId]);\n } else {\n showRank = this.statisticsConfig.teamRankEnabledSports?.length !== 0;\n showPitcher = this.statisticsConfig.pitcherEnabledSports?.length !== 0;\n showRecords = !isEmpty(this.statisticsConfig.teamRecordsFormat);\n }\n\n if (!showRank && !showPitcher && !showRecords) {\n return StatisticsMode.None.toString();\n }\n\n if (showRank && showPitcher && showRecords) {\n return StatisticsMode.All.toString();\n }\n\n if (showRank) {\n statModes.push(StatisticsMode.Rank.toString());\n }\n if (showPitcher) {\n statModes.push(StatisticsMode.Pitchers.toString());\n }\n if (showRecords) {\n statModes.push(StatisticsMode.SeasonStandings.toString());\n }\n\n return statModes.toString();\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Fixture } from '@cds/betting-offer';\nimport { BettingInsightsModel } from '@cds/betting-offer/betting-insights';\nimport { FixtureView, SplitFixture } from '@cds/betting-offer/domain-specific';\nimport { BetBuilderFixture } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { EnhancedFixtureViewGroupingConfiguration } from '@frontend/sports/betting-offer/feature/model';\nimport { OfferGroupingService } from '@frontend/sports/betting-offer/feature/offer-grouping';\nimport { firstValueFrom } from 'rxjs';\n\nimport { BettingOfferApi } from './betting-offer-api.service';\n\nexport interface FixtureViewModel {\n fixture: Fixture;\n splitFixtures: SplitFixture[];\n preCreatedOffer?: BetBuilderFixture;\n grouping: EnhancedFixtureViewGroupingConfiguration;\n bettingInsights?: BettingInsightsModel;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class BettingOfferService {\n constructor(\n private bettingOfferApi: BettingOfferApi,\n private offerGroupingService: OfferGroupingService,\n ) {}\n\n async getFixtureViewModel(\n id: string,\n excludeMarkets: boolean,\n statsMode: string,\n includePrecreated: boolean,\n isBettingInsightsEnabled = false,\n includeRelatedFixtures?: boolean,\n marketGroupIds?: string,\n ): Promise {\n const fixtureView = await this.bettingOfferApi.getFixtureView(\n id,\n statsMode,\n excludeMarkets,\n includePrecreated,\n isBettingInsightsEnabled,\n undefined,\n undefined,\n undefined,\n undefined,\n marketGroupIds,\n includeRelatedFixtures,\n );\n\n return this.createViewModel(fixtureView);\n }\n\n private async createViewModel(fixtureView?: FixtureView): Promise {\n if (!fixtureView?.fixture) {\n return;\n }\n const fixture = fixtureView.fixture;\n\n const grouping = await firstValueFrom(this.offerGroupingService.getFixtureGrouping(fixture.sport.id, fixtureView.groupingVersion));\n\n if (!grouping) {\n throw new Error('Unable to load fixture grouping');\n }\n\n return {\n fixture,\n splitFixtures: fixtureView.splitFixtures,\n preCreatedOffer: fixtureView.preCreatedOffer,\n grouping,\n bettingInsights: fixtureView.bettingInsights,\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { Option, OptionMarket, Price } from '@cds/betting-offer';\nimport { BetBuilderFixture, BetBuilderMarketInfo } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { BetBuilderConfig } from '@frontend/sports/common/client-config-data-access';\nimport { NativePrice } from '@frontend/sports/odds/feature/native-price';\n\nimport { BetBuilderMarket, BetBuilderOption, BetBuilderPrecreatedModel, OptionPrice } from '../model/bet-builder.model';\n\n@Injectable({ providedIn: 'root' })\nexport class BetBuilderMapperService {\n constructor(private config: BetBuilderConfig) {}\n\n mapFixture(fixture?: BetBuilderFixture): BetBuilderPrecreatedModel | undefined {\n if (!fixture || !fixture.sourceFixture) {\n return;\n }\n\n const markets = this.mapMarkets(fixture);\n const fixtureId = fixture.sourceFixture.id;\n\n return {\n fixtureId,\n hasMarkets: fixture.sourceFixture.optionMarkets.length !== 0,\n ambassadorsMarkets: markets.filter((m) => m.ambassador && m.visibleOnBetBuilderTab),\n promotedMarkets: markets.filter((m) => !m.ambassador && m.visibleOnBetBuilderTab),\n themedTabMarkets: markets.filter((m) => m.themedTab),\n };\n }\n\n private mapMarkets(response: BetBuilderFixture): BetBuilderMarket[] {\n if (!response.sourceFixture) {\n return [];\n }\n let markets = response.sourceFixture.optionMarkets.map((market) => this.mapGeneralMarket(market));\n markets = this.mapPrecreatedMarketLongId(response.precreatedMarketsInfo, markets);\n\n return this.mapAmbassador(response.precreatedMarketsInfo, markets);\n }\n\n private mapPrecreatedMarketLongId(precreatedMarketsInfo: BetBuilderMarketInfo[], markets: BetBuilderMarket[]): BetBuilderMarket[] {\n return markets.map((market) => {\n market.longIds = precreatedMarketsInfo.find((marketInfo) => marketInfo.id === market.id)!.selections;\n\n return market;\n });\n }\n\n private mapAmbassador(precreatedMarketsInfo: BetBuilderMarketInfo[], markets: BetBuilderMarket[]): BetBuilderMarket[] {\n precreatedMarketsInfo.forEach((marketInfo) => {\n markets.find((market) => market.id === marketInfo.id)!.visibleOnBetBuilderTab = marketInfo.visibleOnBetBuilderTab;\n if (!marketInfo.ambassadorKey) {\n return;\n }\n\n const ambassador = this.config.ambassadors.find((a) => a.key === marketInfo.ambassadorKey);\n if (!ambassador) {\n return;\n }\n markets.find((market) => market.id === marketInfo.id)!.ambassador = {\n image: ambassador.image,\n name: ambassador.name,\n singleAmbassadorTitle: ambassador.singleAmbassadorTitle,\n };\n });\n\n return markets;\n }\n\n private mapGeneralMarket(responseMarket: OptionMarket): BetBuilderMarket {\n return {\n id: responseMarket.id,\n name: responseMarket.name.value,\n nameSign: responseMarket.name.sign,\n option: this.mapOption(responseMarket.options[0]),\n };\n }\n\n private mapOption(responseOption: Option): BetBuilderOption {\n return {\n id: responseOption.id,\n optionPrice: this.mapOptionPrice(responseOption.price),\n };\n }\n\n private mapOptionPrice(optionPrice: Price): OptionPrice {\n return {\n id: optionPrice.id,\n nativePrice: NativePrice.fromNativePrice(optionPrice),\n };\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { BetBuilderFixture } from '@cds/betting-offer/domain-specific/bet-builder';\nimport { VirtualCompetition, VirtualCompetitionGroup } from '@cds/betting-offer/tags';\nimport { StatisticsMode } from '@cds/query-objects';\nimport { DetailedFixtureFactory, DetailedGroupingFactory } from '@frontend/sports/betting-offer/feature/fixture-factories';\nimport {\n EnhancedFixtureViewGroupingConfiguration,\n EventModel,\n EventOptionGroup,\n EventOptionGroupType,\n EventOptionSet,\n EventOptionSetHeaderTab,\n ExtendedDisplayType,\n MyMarketModel,\n OptionGroup,\n PRECREATED_BAB_EMPTY_THEME_ID,\n ParticipantInfo,\n SetTab,\n getPitcherByParticipantType,\n getRankByParticipantType,\n getWinLossStatsByParticipantType,\n} from '@frontend/sports/betting-offer/feature/model';\nimport { BettingOfferService } from '@frontend/sports/betting-offer/feature/offer-service';\nimport { BetBuilderConfig, BettingInsightsConfiguration } from '@frontend/sports/common/client-config-data-access';\nimport { LocalStoreService, NativeAppService } from '@frontend/vanilla/core';\nimport { includes, isEmpty, maxBy, sumBy, uniq, uniqBy } from 'lodash-es';\n\nimport { BetBuilderMapperService } from '../betbuilder/services/betbuilder-mapper.service';\nimport { StatisticsConfigService } from '../statistics/statistics-config.service';\nimport { PrepareOptionGroupsParams, ResultEventModel, VirtualCompetitionDetails } from './event-details-common.models';\n\n@Injectable({ providedIn: 'root' })\nexport class EventDetailsService {\n private storageKey = 'sia_my_markets';\n participantInfos: ParticipantInfo[];\n teamRecordsEnabled = false;\n teamRanksEnabled = false;\n teamPitchersEnabled = false;\n\n constructor(\n private bettingOffer: BettingOfferService,\n private fixtureFactory: DetailedFixtureFactory,\n private groupingFactory: DetailedGroupingFactory,\n private betBuilderMapper: BetBuilderMapperService,\n private betBuilderConfig: BetBuilderConfig,\n private bettingInsightsConfig: BettingInsightsConfiguration,\n public statisticsConfigService: StatisticsConfigService,\n private localStore: LocalStoreService,\n private nativeAppService: NativeAppService,\n ) {}\n\n async getEvent(\n id: string,\n excludeMarkets: boolean = false,\n includeRelatedFixtures?: boolean,\n marketGroupIds?: string,\n ): Promise {\n const cdsEntity = await this.bettingOffer.getFixtureViewModel(\n id,\n excludeMarkets,\n StatisticsMode.None,\n false,\n this.bettingInsightsConfig.isEnabled,\n includeRelatedFixtures,\n marketGroupIds,\n );\n if (!cdsEntity) {\n return;\n }\n\n return this.fixtureFactory.create({\n fixture: cdsEntity.fixture,\n splitFixtures: cdsEntity.splitFixtures,\n grouping: cdsEntity.grouping,\n loadEventSitemap: true,\n bettingInsights: cdsEntity.bettingInsights,\n });\n }\n\n async getModel(id: string): Promise {\n const statsMode = this.statisticsConfigService.getStatisticsModesBySportId();\n const cdsEntity = await this.bettingOffer.getFixtureViewModel(\n id,\n false,\n statsMode,\n this.betBuilderConfig.isPrecreatedBetBuilderEnabled,\n this.bettingInsightsConfig.isEnabled,\n );\n if (!cdsEntity) {\n return;\n }\n\n this.addBetbuilderToGrouping(cdsEntity.grouping, cdsEntity.preCreatedOffer);\n\n return {\n model: this.fixtureFactory.create({\n fixture: cdsEntity.fixture,\n splitFixtures: cdsEntity.splitFixtures,\n grouping: cdsEntity.grouping,\n precreated: cdsEntity.preCreatedOffer,\n loadEventSitemap: true,\n bettingInsights: cdsEntity.bettingInsights,\n excludePriceboostedMarketGrouping: !this.nativeAppService.isTerminal,\n }),\n preCreated: this.betBuilderMapper.mapFixture(cdsEntity.preCreatedOffer),\n virtualCompetitionDetails: this.mapVirtualCompetitionDetails(\n cdsEntity.fixture.virtualCompetition,\n cdsEntity.fixture.virtualCompetitionGroup,\n ),\n fixture: cdsEntity.fixture,\n betBuilderFixture: cdsEntity.preCreatedOffer,\n };\n }\n\n private mapVirtualCompetitionDetails(\n virtualCompetition?: VirtualCompetition,\n virtualCompetitionGroup?: VirtualCompetitionGroup,\n ): VirtualCompetitionDetails | undefined {\n if (!virtualCompetition) {\n return;\n }\n\n return {\n id: virtualCompetition.id,\n name: virtualCompetition.name.value,\n group: virtualCompetitionGroup ? { id: virtualCompetitionGroup.id, name: virtualCompetitionGroup.name.value } : undefined,\n };\n }\n\n addBetbuilderToGrouping(\n grouping: EnhancedFixtureViewGroupingConfiguration,\n preCreatedOffer?: BetBuilderFixture,\n ): EnhancedFixtureViewGroupingConfiguration {\n const themedTabs = uniq(preCreatedOffer?.precreatedMarketsInfo.map((x) => x.themedTab?.toUpperCase()));\n const sportSpecificThemedTabs = this.betBuilderConfig.themedTabs[grouping.sportId] || [];\n const themedTabNames = sportSpecificThemedTabs.filter((i) => includes(themedTabs, i.key?.toUpperCase().trim())).map((s) => s.name);\n const marketTabId = maxBy(grouping.marketTabs.map((x) => x.id));\n if (marketTabId) {\n let count = marketTabId + 1;\n if (themedTabNames?.length) {\n themedTabNames.forEach((tabs) => {\n grouping.marketTabs.push({ id: count, name: tabs || '', sortOrder: count });\n count++;\n });\n }\n\n grouping.marketTabs.push({ id: count, name: '', sortOrder: count });\n }\n\n return grouping;\n }\n\n prepareEventOptionGroups(params: PrepareOptionGroupsParams): EventOptionGroup[] {\n const { event, groupedSetsIds, tag, setTags, isSgp } = params;\n const activeTab = params.activeTab ?? SetTab.All;\n let optionGroups = [];\n\n if (activeTab === SetTab.MyMarkets) {\n optionGroups = this.getOptionGroupForMyMarkets(event);\n } else if (groupedSetsIds && groupedSetsIds.length > 0) {\n optionGroups = this.getMultipleOptionGroups(event.optionGroups, groupedSetsIds);\n\n if (setTags && setTags.length >= 1) {\n optionGroups = optionGroups.filter(\n (group) =>\n group.detailedGrouping?.tags &&\n group.detailedGrouping.tags.length >= setTags.length &&\n setTags.every((t, index) => t === group.detailedGrouping.tags![index].key) &&\n (isSgp ? group.isBetBuilder : true),\n );\n }\n } else {\n optionGroups =\n activeTab === SetTab.All\n ? event.optionGroups\n : event.optionGroups.filter((group) => group.detailedGrouping && group.detailedGrouping.marketSet === activeTab);\n\n if (tag) {\n optionGroups = optionGroups.filter((group) => group.detailedGrouping?.tags?.length && group.detailedGrouping.tags[0].key === tag);\n }\n\n if (isSgp) {\n optionGroups = optionGroups.filter((group) => group.isBetBuilder);\n }\n }\n\n optionGroups = this.distinctOptionGroups(optionGroups);\n\n if (!params.isPrecreatedBABTab) {\n optionGroups = this.filterBetBuilderThemedOptionGroups(optionGroups);\n }\n\n if (params.isPrecreatedBABTab) {\n if (params.event.precreatedOptionGroups?.length) {\n return this.groupingFactory.transformEventGroupsPerPrecreatedGroup(optionGroups, params.event.precreatedOptionGroups);\n }\n\n return this.groupingFactory.groupMarkets(optionGroups, true, true);\n }\n\n return this.groupingFactory.groupMarkets(optionGroups);\n }\n\n private filterBetBuilderThemedOptionGroups(optionGroups: EventOptionGroup[]): EventOptionGroup[] {\n const isThemeTabAssigned = optionGroups.some(\n (og) =>\n og.detailedGrouping.displayType === ExtendedDisplayType.BetBuilder &&\n og.detailedGrouping.marketTabId !== PRECREATED_BAB_EMPTY_THEME_ID,\n );\n if (isThemeTabAssigned)\n return optionGroups.filter((optionGroup) => optionGroup.detailedGrouping.marketTabId !== PRECREATED_BAB_EMPTY_THEME_ID);\n\n return optionGroups;\n }\n\n isPreCreatedBABTab(event: EventModel, activeTab: string | SetTab = SetTab.All): boolean {\n return event.optionSets.find((t) => t.id === Number(activeTab))?.isPrecreated!;\n }\n\n getOptionGroupForMyMarkets(event: EventModel): EventOptionGroup[] {\n const myMarkets = this.localStore.get(this.storageKey) as MyMarketModel;\n if ((event.live && myMarkets?.live) || (!event.live && myMarkets?.prematch)) {\n //@ts-ignore allow dynamic accessor property, please don't use this, instead, use typing.\n const sportMarkets = event.live ? myMarkets.live[event.sport.id] : myMarkets.prematch[event.sport.id];\n\n if (sportMarkets) {\n return event.optionGroups.filter((group) => {\n if (group.detailedGrouping) {\n const market = sportMarkets.find(\n (sMarket: OptionGroup) =>\n sMarket.market_group_id === group.detailedGrouping.marketSet &&\n sMarket.tv1_market_group_item === group.detailedGrouping.marketOrder,\n );\n if (market) {\n group.position = market.position;\n\n return true;\n }\n\n return false;\n }\n\n return false;\n });\n }\n }\n\n return [];\n }\n\n prepareEventOptionSetHeaderTab(set: EventOptionSet, optionGroups: EventOptionGroup[]): EventOptionSetHeaderTab {\n return {\n id: set.id,\n name: set.name,\n count: optionGroups.length,\n optionGroups,\n };\n }\n\n calculateEventOptionGroupsCount(event: EventModel): number {\n const optionGroups = this.prepareEventOptionGroups({ event });\n\n return sumBy(optionGroups, (value) => value.getSubGroups(EventOptionGroupType.Period).length || 1);\n }\n\n private distinctOptionGroups(optionGroups: EventOptionGroup[]): EventOptionGroup[] {\n return uniqBy(\n optionGroups,\n (item) =>\n `${item.id}-${item.templateId}-${item.detailedGrouping.displayType}${item.detailedGrouping.sixPackGridId ? '-' + item.detailedGrouping.sixPackGridId : ''}`,\n );\n }\n\n private getMultipleOptionGroups(optionGroups: EventOptionGroup[], groupIds: number[]): EventOptionGroup[] {\n const groups: EventOptionGroup[] = [];\n groupIds.forEach((g) => {\n groups.push(...optionGroups.filter((group) => group.detailedGrouping && group.detailedGrouping.marketSet === g));\n });\n\n return groups;\n }\n\n setParticipantInfos(event: EventModel): ParticipantInfo[] {\n this.teamPitchersEnabled = !!this.statisticsConfigService.pitcherEnabledSports[event.sport.id];\n this.teamRecordsEnabled = !isEmpty(this.statisticsConfigService.teamRecordsFormat[event.sport.id]);\n this.teamRanksEnabled = !!this.statisticsConfigService.teamRankEnabledSports[event.sport.id];\n\n return (this.participantInfos = event.participants.map((participant) => {\n const rank = this.teamRanksEnabled ? getRankByParticipantType(event, participant.type) : undefined;\n const pitcher = !event.scoreboard.started && this.teamPitchersEnabled ? getPitcherByParticipantType(event, participant.type) : undefined;\n const teamRecordsFormat = this.statisticsConfigService.teamRecordsFormat[event.sport.id];\n const winLossStats =\n !event.scoreboard.started && this.teamRecordsEnabled\n ? getWinLossStatsByParticipantType(event, participant.type!, teamRecordsFormat)\n : undefined;\n\n return {\n participant,\n rank,\n winLossStats,\n pitcher,\n } as ParticipantInfo;\n }));\n }\n}\n","export enum ParticipantPickType {\n Win = 1,\n Forecast = 2,\n CombinationForecast = 3,\n Tricast = 4,\n CombinationTricast = 5,\n Freeform = 6,\n}\n\nexport enum SourceOfPick {\n Upsell = 'Upsell',\n OutrightsGrid = 'OutrightsGrid',\n Other = 'Other',\n BetStationMarquee = 'BetStationMarquee',\n EventDetails = 'EventDetails',\n LineSwitcher = 'LineSwitcher',\n MultiGenerator = 'MultiGenerator',\n WorldCupHub = 'WorldCupHub',\n EmbeddedBetBuilder = 'EmbeddedBetBuilder',\n OverlayBetBuilder = 'OverlayBetBuilder',\n TeamGrouping = 'TeamGrouping',\n}\n","import { ChangeDetectorRef, ElementRef, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { type Observable, debounceTime, filter, fromEvent, merge, of, switchMap, tap } from 'rxjs';\n\nexport function rxHostListener(event: string): Observable {\n const cdr = inject(ChangeDetectorRef);\n\n // Listen to event\n return fromEvent(inject(ElementRef).nativeElement, event).pipe(\n debounceTime(0),\n tap(() => cdr.markForCheck()), // Trigger CD like @HostListener would\n takeUntilDestroyed(), // Unsubscribe\n );\n}\n\n/*\n * @internal\n **/\nexport function rxHostPressedListener(): Observable {\n return merge(\n rxHostListener('click'),\n rxHostListener('keyup').pipe(\n switchMap((x) => {\n return x.code === 'Space' || x.code === 'Enter' ? of(true) : of(null);\n }),\n filter(Boolean),\n ),\n ).pipe(debounceTime(0));\n}\n","/**\n * @license Angular v18.2.6\n * (c) 2010-2024 Google LLC. https://angular.io/\n * License: MIT\n */\n\nimport * as i0 from '@angular/core';\nimport { Directive, InjectionToken, forwardRef, Optional, Inject, ɵisPromise, ɵisSubscribable, ɵRuntimeError, Self, computed, signal, untracked, EventEmitter, Input, Host, SkipSelf, booleanAttribute, ChangeDetectorRef, Output, Injectable, inject, NgModule, Version } from '@angular/core';\nimport { ɵgetDOM } from '@angular/common';\nimport { from, forkJoin, Subject } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\n/**\n * Base class for all ControlValueAccessor classes defined in Forms package.\n * Contains common logic and utility functions.\n *\n * Note: this is an *internal-only* class and should not be extended or used directly in\n * applications code.\n */\nlet BaseControlValueAccessor = /*#__PURE__*/(() => {\n class BaseControlValueAccessor {\n constructor(_renderer, _elementRef) {\n this._renderer = _renderer;\n this._elementRef = _elementRef;\n /**\n * The registered callback function called when a change or input event occurs on the input\n * element.\n * @nodoc\n */\n this.onChange = _ => {};\n /**\n * The registered callback function called when a blur event occurs on the input element.\n * @nodoc\n */\n this.onTouched = () => {};\n }\n /**\n * Helper method that sets a property on a target element using the current Renderer\n * implementation.\n * @nodoc\n */\n setProperty(key, value) {\n this._renderer.setProperty(this._elementRef.nativeElement, key, value);\n }\n /**\n * Registers a function called when the control is touched.\n * @nodoc\n */\n registerOnTouched(fn) {\n this.onTouched = fn;\n }\n /**\n * Registers a function called when the control value changes.\n * @nodoc\n */\n registerOnChange(fn) {\n this.onChange = fn;\n }\n /**\n * Sets the \"disabled\" property on the range input element.\n * @nodoc\n */\n setDisabledState(isDisabled) {\n this.setProperty('disabled', isDisabled);\n }\n static {\n this.ɵfac = function BaseControlValueAccessor_Factory(__ngFactoryType__) {\n return new (__ngFactoryType__ || BaseControlValueAccessor)(i0.ɵɵdirectiveInject(i0.Renderer2), i0.ɵɵdirectiveInject(i0.ElementRef));\n };\n }\n static {\n this.ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: BaseControlValueAccessor\n });\n }\n }\n return BaseControlValueAccessor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Base class for all built-in ControlValueAccessor classes (except DefaultValueAccessor, which is\n * used in case no other CVAs can be found). We use this class to distinguish between default CVA,\n * built-in CVAs and custom CVAs, so that Forms logic can recognize built-in CVAs and treat custom\n * ones with higher priority (when both built-in and custom CVAs are present).\n *\n * Note: this is an *internal-only* class and should not be extended or used directly in\n * applications code.\n */\nlet BuiltInControlValueAccessor = /*#__PURE__*/(() => {\n class BuiltInControlValueAccessor extends BaseControlValueAccessor {\n static {\n this.ɵfac = /* @__PURE__ */(() => {\n let ɵBuiltInControlValueAccessor_BaseFactory;\n return function BuiltInControlValueAccessor_Factory(__ngFactoryType__) {\n return (ɵBuiltInControlValueAccessor_BaseFactory || (ɵBuiltInControlValueAccessor_BaseFactory = i0.ɵɵgetInheritedFactory(BuiltInControlValueAccessor)))(__ngFactoryType__ || BuiltInControlValueAccessor);\n };\n })();\n }\n static {\n this.ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: BuiltInControlValueAccessor,\n features: [i0.ɵɵInheritDefinitionFeature]\n });\n }\n }\n return BuiltInControlValueAccessor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n/**\n * Used to provide a `ControlValueAccessor` for form controls.\n *\n * See `DefaultValueAccessor` for how to implement one.\n *\n * @publicApi\n */\nconst NG_VALUE_ACCESSOR = /*#__PURE__*/new InjectionToken(ngDevMode ? 'NgValueAccessor' : '');\nconst CHECKBOX_VALUE_ACCESSOR = {\n provide: NG_VALUE_ACCESSOR,\n useExisting: /*#__PURE__*/forwardRef(() => CheckboxControlValueAccessor),\n multi: true\n};\n/**\n * @description\n * A `ControlValueAccessor` for writing a value and listening to changes on a checkbox input\n * element.\n *\n * @usageNotes\n *\n * ### Using a checkbox with a reactive form.\n *\n * The following example shows how to use a checkbox with a reactive form.\n *\n * ```ts\n * const rememberLoginControl = new FormControl();\n * ```\n *\n * ```\n * \n * ```\n *\n * @ngModule ReactiveFormsModule\n * @ngModule FormsModule\n * @publicApi\n */\nlet CheckboxControlValueAccessor = /*#__PURE__*/(() => {\n class CheckboxControlValueAccessor extends BuiltInControlValueAccessor {\n /**\n * Sets the \"checked\" property on the input element.\n * @nodoc\n */\n writeValue(value) {\n this.setProperty('checked', value);\n }\n static {\n this.ɵfac = /* @__PURE__ */(() => {\n let ɵCheckboxControlValueAccessor_BaseFactory;\n return function CheckboxControlValueAccessor_Factory(__ngFactoryType__) {\n return (ɵCheckboxControlValueAccessor_BaseFactory || (ɵCheckboxControlValueAccessor_BaseFactory = i0.ɵɵgetInheritedFactory(CheckboxControlValueAccessor)))(__ngFactoryType__ || CheckboxControlValueAccessor);\n };\n })();\n }\n static {\n this.ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: CheckboxControlValueAccessor,\n selectors: [[\"input\", \"type\", \"checkbox\", \"formControlName\", \"\"], [\"input\", \"type\", \"checkbox\", \"formControl\", \"\"], [\"input\", \"type\", \"checkbox\", \"ngModel\", \"\"]],\n hostBindings: function CheckboxControlValueAccessor_HostBindings(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵlistener(\"change\", function CheckboxControlValueAccessor_change_HostBindingHandler($event) {\n return ctx.onChange($event.target.checked);\n })(\"blur\", function CheckboxControlValueAccessor_blur_HostBindingHandler() {\n return ctx.onTouched();\n });\n }\n },\n features: [i0.ɵɵProvidersFeature([CHECKBOX_VALUE_ACCESSOR]), i0.ɵɵInheritDefinitionFeature]\n });\n }\n }\n return CheckboxControlValueAccessor;\n})();\n/*#__PURE__*/(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nconst DEFAULT_VALUE_ACCESSOR = {\n provide: NG_VALUE_ACCESSOR,\n useExisting: /*#__PURE__*/forwardRef(() => DefaultValueAccessor),\n multi: true\n};\n/**\n * We must check whether the agent is Android because composition events\n * behave differently between iOS and Android.\n */\nfunction _isAndroid() {\n const userAgent = ɵgetDOM() ? ɵgetDOM().getUserAgent() : '';\n return /android (\\d+)/.test(userAgent.toLowerCase());\n}\n/**\n * @description\n * Provide this token to control if form directives buffer IME input until\n * the \"compositionend\" event occurs.\n * @publicApi\n */\nconst COMPOSITION_BUFFER_MODE = /*#__PURE__*/new InjectionToken(ngDevMode ? 'CompositionEventMode' : '');\n/**\n * The default `ControlValueAccessor` for writing a value and listening to changes on input\n * elements. The accessor is used by the `FormControlDirective`, `FormControlName`, and\n * `NgModel` directives.\n *\n *\n * @usageNotes\n *\n * ### Using the default value accessor\n *\n * The following example shows how to use an input element that activates the default value accessor\n * (in this case, a text field).\n *\n * ```ts\n * const firstNameControl = new FormControl();\n * ```\n *\n * ```\n * \n * ```\n *\n * This value accessor is used by default for `` and `