Files
thebet365/apps/player/src/views/MobileOutrightDetailView.vue

167 lines
4.0 KiB
Vue

<script setup lang="ts">
import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useOutrightEvents } from '../composables/useOutrightEvents';
import type { OutrightSelection } from '../components/outright/OutrightEventSection.vue';
import OutrightOptionCard from '../components/outright/OutrightOptionCard.vue';
import OutrightBetModal, { type OutrightPick } from '../components/outright/OutrightBetModal.vue';
import { useAuthStore } from '../stores/auth';
import GoldSpinner from '../components/GoldSpinner.vue';
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const auth = useAuthStore();
const { loading, loadError, events, load } = useOutrightEvents();
const modalOpen = ref(false);
const activePick = ref<OutrightPick | null>(null);
const event = computed(() => events.value.find((e) => e.id === String(route.params.id)) ?? null);
const headTitle = computed(() => {
if (!event.value) return '';
const raw = event.value.title.replace(/^\*+/, '').trim();
return raw || event.value.leagueName || t('bet.tab_outright');
});
const isSettled = computed(
() => event.value?.bettingOpen === false || event.value?.status === 'SETTLED',
);
function goBack() {
router.push({ path: '/bet', query: { tab: 'outright' } });
}
function openBet(sel: OutrightSelection) {
if (!event.value || isSettled.value) return;
if (!auth.token) {
auth.showLoginPrompt(route.fullPath);
return;
}
activePick.value = {
selectionId: sel.id,
oddsVersion: sel.oddsVersion,
teamCode: sel.teamCode,
teamName: sel.teamName,
odds: sel.odds,
eventTitle: event.value.title,
};
modalOpen.value = true;
}
function closeModal() {
modalOpen.value = false;
activePick.value = null;
}
let pollTimer: ReturnType<typeof setInterval> | null = null;
watch(
() => event.value?.id,
(id) => {
if (pollTimer) clearInterval(pollTimer);
if (id) {
pollTimer = setInterval(() => void load({ silent: true }), 15000);
}
},
);
onMounted(() => {
if (event.value) {
pollTimer = setInterval(() => void load({ silent: true }), 15000);
}
});
onUnmounted(() => {
if (pollTimer) clearInterval(pollTimer);
});
</script>
<template>
<div class="mobile-outright-detail">
<header class="top-bar">
<button type="button" class="back-btn" @click="goBack"> {{ t('bet.back') }}</button>
<h1>{{ headTitle || t('bet.tab_outright') }}</h1>
</header>
<div v-if="loading && !event" class="state">
<GoldSpinner :size="36" />
</div>
<template v-else-if="event">
<p v-if="isSettled" class="settled-hint">{{ t('bet.outright_settled_hint') }}</p>
<div class="teams-grid">
<OutrightOptionCard
v-for="sel in event.selections"
:key="sel.id"
:team-code="sel.teamCode"
:team-name="sel.teamName"
:logo-url="sel.logoUrl"
:odds="sel.odds"
:disabled="isSettled"
:is-winner="Boolean(sel.isWinner)"
@pick="openBet(sel)"
/>
</div>
</template>
<div v-else class="state">
<p>{{ loadError || t('bet.no_outright') }}</p>
</div>
<OutrightBetModal :open="modalOpen" :pick="activePick" @close="closeModal" />
</div>
</template>
<style scoped>
.mobile-outright-detail {
padding: 12px;
}
.top-bar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.top-bar h1 {
margin: 0;
font-size: 15px;
font-weight: 800;
color: var(--primary-light);
}
.back-btn {
background: transparent;
color: var(--primary-light);
font-weight: 700;
font-size: 13px;
}
.settled-hint {
margin: 0 0 12px;
padding: 8px 10px;
border-radius: 8px;
background: rgba(201, 162, 39, 0.08);
border: 1px solid rgba(201, 162, 39, 0.22);
font-size: 12px;
font-weight: 600;
color: #c9a227;
}
.teams-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 6px;
}
.state {
text-align: center;
padding: 40px 16px;
color: var(--text-muted);
}
</style>