-
-
-
-
{{ t('bet.guide_title') }}
-
-
-
-
{{ t('bet.guide_flow_normal') }}
-
- - {{ t('bet.guide_normal_1') }}
- - {{ t('bet.guide_normal_2') }}
- - {{ t('bet.guide_normal_3') }}
-
-
-
-
{{ t('bet.guide_flow_cs') }}
-
- - {{ t('bet.guide_cs_1') }}
- - {{ t('bet.guide_cs_2') }}
- - {{ t('bet.guide_cs_3') }}
-
-
-
-
-
-
+
+
{{ t('bet.guide_flow_normal') }}
+
+ - {{ t('bet.guide_normal_1') }}
+ - {{ t('bet.guide_normal_2') }}
+ - {{ t('bet.guide_normal_3') }}
+
-
+
+
{{ t('bet.guide_flow_cs') }}
+
+ - {{ t('bet.guide_cs_1') }}
+ - {{ t('bet.guide_cs_2') }}
+ - {{ t('bet.guide_cs_3') }}
+
+
+
+
{{ t('bet.guide_flow_parlay') }}
+
{{ t('bet.guide_parlay_1') }}
+
+
{{ t('bet.guide_rules_link') }}
+
-
-
diff --git a/apps/player/src/components/parlay/ParlayPanel.vue b/apps/player/src/components/parlay/ParlayPanel.vue
index 041af38..4a880de 100644
--- a/apps/player/src/components/parlay/ParlayPanel.vue
+++ b/apps/player/src/components/parlay/ParlayPanel.vue
@@ -3,7 +3,9 @@ import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import api from '../../api';
import { useBetSlipStore } from '../../stores/betSlip';
-import { PARLAY_MARKET_TYPES, SELECTION_SHORT } from '../../utils/parlayColumns';
+import { PARLAY_MAX_LEGS, canSelectForParlay } from '@thebet365/shared';
+import { PARLAY_MARKET_TYPES, PARLAY_SELECTION_KEYS } from '../../utils/parlayColumns';
+import BetGuideHelp from '../BetGuideHelp.vue';
type TimeFilter = 'all' | 'today';
@@ -18,6 +20,8 @@ interface Selection {
interface Market {
id: string;
marketType: string;
+ lineValue?: string | number | null;
+ allowParlay?: boolean;
selections: Selection[];
}
@@ -51,10 +55,25 @@ onMounted(async () => {
}
});
+function parseLine(v: string | number | null | undefined) {
+ if (v == null || v === '') return null;
+ const n = typeof v === 'number' ? v : parseFloat(String(v));
+ return Number.isFinite(n) ? n : null;
+}
+
+function isParlayEligibleMarket(market: Market) {
+ if (!market.selections.length) return false;
+ return canSelectForParlay({
+ marketType: market.marketType,
+ lineValue: parseLine(market.lineValue),
+ allowParlay: market.allowParlay ?? true,
+ }).ok;
+}
+
function hasParlayMarkets(m: ParlayMatch) {
return parlayMarketKeys.some((key) => {
const market = m.markets?.find((mk) => mk.marketType === key);
- return market && market.selections.length > 0;
+ return market && isParlayEligibleMarket(market);
});
}
@@ -98,15 +117,23 @@ function formatOdds(odds: string) {
}
function selLabel(sel: Selection) {
- return SELECTION_SHORT[sel.selectionCode] ?? sel.selectionName.slice(0, 2);
+ const key = PARLAY_SELECTION_KEYS[sel.selectionCode];
+ if (key) return t(`bet.${key}`);
+ return sel.selectionName.slice(0, 2);
+}
+
+function colLabel(labelKey: string) {
+ return t(`bet.${labelKey}`);
}
function isPicked(selectionId: string) {
return slip.items.some((i) => i.selectionId === selectionId);
}
+const parlayHint = ref('');
+
function pickSelection(match: ParlayMatch, market: Market, sel: Selection) {
- slip.addParlayLeg({
+ const err = slip.addParlayLeg({
selectionId: sel.id,
oddsVersion: String(sel.oddsVersion),
matchId: match.id,
@@ -114,7 +141,14 @@ function pickSelection(match: ParlayMatch, market: Market, sel: Selection) {
selectionName: `${selLabel(sel)} ${formatOdds(sel.odds)}`,
odds: parseFloat(sel.odds),
marketType: market.marketType,
+ lineValue: parseLine(market.lineValue),
+ allowParlay: market.allowParlay,
});
+ if (err === 'MAX_LEGS') parlayHint.value = t('bet.parlay_max_legs');
+ else if (err === 'QUARTER_LINE') parlayHint.value = t('bet.parlay_block_quarter');
+ else if (err === 'OUTRIGHT') parlayHint.value = t('bet.parlay_block_outright');
+ else if (err === 'NOT_ALLOWED') parlayHint.value = t('bet.parlay_block_not_allowed');
+ else parlayHint.value = '';
}
function openSlip() {
@@ -126,21 +160,28 @@ const showParlayFoot = computed(() => slip.mode === 'parlay' && slip.count > 0);
-
-
@@ -159,7 +200,12 @@ const showParlayFoot = computed(() => slip.mode === 'parlay' && slip.count > 0);
:key="col.key"
class="market-cell"
>
-
+