Implement Smart Location: auto-detect user location if preference is enabled, hide location button, and handle permission denial by resetting preference

This commit is contained in:
2026-03-01 12:15:08 -05:00
parent d0d75b8c98
commit 4d7b472c6c
20 changed files with 852 additions and 344 deletions

View File

@ -5,9 +5,11 @@ import { supabase } from '@/supabase'
import type { Shuttle } from '@/types'
import { getImageUrl } from '@/utils/imageUrl'
import { analyticsService } from '@/services/analyticsService'
import { useI18n } from 'vue-i18n'
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const shuttle = ref<Shuttle | null>(null)
const cargando = ref(true)
const error = ref<string | null>(null)
@ -27,7 +29,7 @@ onMounted(async () => {
if (sbError) throw sbError
shuttle.value = data
} catch (e: any) {
error.value = 'No se pudo cargar la información del viaje'
error.value = t('shuttle.errorLoading')
console.error('SIBU | Error cargando shuttle:', e)
} finally {
cargando.value = false
@ -51,6 +53,13 @@ const volver = () => {
router.push('/transporte/viajes-turisticos')
}
}
const getTripTypeLabel = (type: string) => {
if (type === 'one_way') return t('shuttle.oneWay')
if (type === 'round_trip') return t('shuttle.roundTrip')
if (type === 'both') return t('shuttle.both')
return type
}
</script>
<template>
@ -61,14 +70,14 @@ const volver = () => {
<span class="material-icons text-[var(--text-primary)]">arrow_back</span>
</button>
<h1 class="font-bold text-[var(--text-primary)] text-lg truncate flex-1">
{{ shuttle?.company_name || 'Detalle del viaje' }}
{{ shuttle?.company_name || t('shuttle.detailTitle') }}
</h1>
</div>
<!-- Loading -->
<div v-if="cargando" class="flex flex-col justify-center items-center h-64 gap-3">
<span class="material-icons spin text-4xl" style="color: var(--active-color)">refresh</span>
<p class="text-text-secondary font-medium animate-pulse">Cargando...</p>
<p class="text-text-secondary font-medium animate-pulse">{{ t('common.loading') }}</p>
</div>
<!-- Error -->
@ -76,7 +85,7 @@ const volver = () => {
<span class="material-icons text-red-500 text-5xl mb-3">error_outline</span>
<p class="text-red-500 font-medium">{{ error }}</p>
<button @click="volver" class="mt-6 px-6 py-2 bg-text-primary text-surface font-bold rounded-full shadow hover:opacity-90 transition">
Volver
{{ t('common.back') }}
</button>
</div>
@ -100,7 +109,7 @@ const volver = () => {
<div class="bg-[var(--bg-secondary)] rounded-2xl p-5 shadow-sm space-y-3 border border-border">
<div class="flex items-center justify-between">
<div class="flex flex-col flex-1">
<span class="text-xs text-[var(--text-secondary)] font-semibold mb-1 uppercase tracking-wider">Origen</span>
<span class="text-xs text-[var(--text-secondary)] font-semibold mb-1 uppercase tracking-wider">{{ t('shuttle.origin') }}</span>
<span class="font-bold text-[var(--text-primary)] text-lg leading-tight break-words">
{{ shuttle.origin }}
</span>
@ -114,7 +123,7 @@ const volver = () => {
</div>
<div class="flex flex-col flex-1 text-right">
<span class="text-xs text-[var(--text-secondary)] font-semibold mb-1 uppercase tracking-wider">Destino</span>
<span class="text-xs text-[var(--text-secondary)] font-semibold mb-1 uppercase tracking-wider">{{ t('shuttle.destination') }}</span>
<span class="font-bold text-[var(--text-primary)] text-lg leading-tight break-words">
{{ shuttle.destination }}
</span>
@ -131,19 +140,19 @@ const volver = () => {
<div class="grid grid-cols-2 gap-4 pt-4 border-t border-border">
<div class="flex flex-col gap-1">
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">schedule</span> Hora de salida</span>
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">schedule</span> {{ t('shuttle.departureTimes') }}</span>
<span class="font-semibold text-[var(--text-primary)] bg-[var(--bg-primary)] p-2 rounded-lg text-sm text-center border border-border">
{{ shuttle.departure_times }}
</span>
</div>
<div class="flex flex-col gap-1">
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">swap_horiz</span> Tipo de viaje</span>
<span class="font-semibold text-[var(--text-primary)] bg-[var(--bg-primary)] p-2 rounded-lg text-sm text-center border border-border capitalize">
{{ shuttle.trip_type.replace('_', ' ') }}
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">swap_horiz</span> {{ t('shuttle.tripType') }}</span>
<span class="font-semibold text-[var(--text-primary)] bg-[var(--bg-primary)] p-2 rounded-lg text-sm text-center border border-border">
{{ getTripTypeLabel(shuttle.trip_type) }}
</span>
</div>
<div class="flex flex-col gap-1" v-if="shuttle.english_speaking">
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">g_translate</span> Idiomas</span>
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">g_translate</span> {{ t('shuttle.languages') }}</span>
<span class="font-semibold text-[var(--text-primary)] bg-[var(--bg-primary)] p-2 rounded-lg text-sm text-center border border-border">
Español · English
</span>
@ -154,14 +163,14 @@ const volver = () => {
<!-- Precio -->
<div class="rounded-2xl p-6 shadow-sm flex items-center justify-between" style="background-color: var(--active-color); color: #101820;">
<div class="text-left">
<p class="text-sm font-semibold opacity-90 mb-1">Precio por pasajero</p>
<p class="text-sm font-semibold opacity-90 mb-1">{{ t('shuttle.pricePerPerson') }}</p>
<div class="flex items-baseline gap-1">
<span class="text-lg font-bold opacity-80">$</span>
<span class="text-4xl font-black tracking-tight">{{ parsePrice(shuttle.price_per_person) }}</span>
</div>
</div>
<div class="p-3 rounded-xl bg-black/10 backdrop-blur-sm" v-if="shuttle.price_private_trip">
<span class="text-xs font-bold uppercase tracking-wider opacity-90 block mb-1">Privado</span>
<span class="text-xs font-bold uppercase tracking-wider opacity-90 block mb-1">{{ t('shuttle.private') }}</span>
<span class="font-black text-lg">${{ parsePrice(shuttle.price_private_trip) }}</span>
</div>
</div>
@ -169,8 +178,8 @@ const volver = () => {
<!-- Contacto -->
<div class="bg-[var(--bg-secondary)] rounded-2xl p-5 shadow-sm space-y-4 border border-border mb-8">
<div>
<h3 class="font-bold text-[var(--text-primary)] text-lg">Reserva e Información</h3>
<p class="text-sm text-[var(--text-secondary)] mt-1">Contacta directamente al operador para confirmar disponibilidad.</p>
<h3 class="font-bold text-[var(--text-primary)] text-lg">{{ t('shuttle.bookingInfo') }}</h3>
<p class="text-sm text-[var(--text-secondary)] mt-1">{{ t('shuttle.contactOperator') }}</p>
</div>
<div class="flex flex-col gap-3">
@ -181,7 +190,7 @@ const volver = () => {
@click="analyticsService.logEvent({ event_name: 'shuttle_contact', item_id: shuttle.id, properties: { action: 'whatsapp' } })"
>
<span class="material-icons">chat</span>
Reservar por WhatsApp
{{ t('shuttle.bookWhatsapp') }}
</a>
<a v-if="shuttle.phone_number"
@ -190,7 +199,7 @@ const volver = () => {
@click="analyticsService.logEvent({ event_name: 'shuttle_contact', item_id: shuttle.id, properties: { action: 'call' } })"
>
<span class="material-icons">phone_in_talk</span>
Llamar al Operador
{{ t('shuttle.callOperator') }}
</a>
</div>
</div>

View File

@ -95,7 +95,7 @@ function getShiftLabel(shift: string) {
<p>{{ taxiStore.error }}</p>
<button class="retry-btn" @click="taxiStore.loadTaxis()">
<span class="material-icons">refresh</span>
Reintentar
{{ t('common.retry') || 'Reintentar' }}
</button>
</div>