Initial commit: Vuelato - buscador de vuelos
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Nuxt 4 + Supabase + Flightics API. Incluye búsqueda de vuelos, inspiraciones, watchlist, tracking de precios y mapa interactivo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
117
app/pages/detail/[token].vue
Normal file
117
app/pages/detail/[token].vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import type { Trip } from '~/server/utils/flightics'
|
||||
|
||||
const route = useRoute()
|
||||
const { getDetail } = useFlightSearch()
|
||||
|
||||
const token = computed(() => route.params.token as string)
|
||||
const originalPrice = computed(() => Number(route.query.price) || 0)
|
||||
const passengers = computed(() => ({
|
||||
adult: Number(route.query.adults) || 1,
|
||||
child: Number(route.query.children) || 0,
|
||||
infant: Number(route.query.infants) || 0
|
||||
}))
|
||||
|
||||
const trip = ref<Trip | null>(null)
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const routeSummary = computed(() => {
|
||||
if (!trip.value) return ''
|
||||
const legs = trip.value.legs
|
||||
const codes = legs.map(l => l.segments[0]?.departureCode).filter(Boolean)
|
||||
const lastArr = legs[legs.length - 1]?.segments.at(-1)?.arrivalCode
|
||||
if (lastArr) codes.push(lastArr)
|
||||
return codes.join(' > ')
|
||||
})
|
||||
|
||||
const departureCode = computed(() => trip.value?.legs[0]?.segments[0]?.departureCode ?? '')
|
||||
const arrivalCode = computed(() => trip.value?.legs[0]?.segments.at(-1)?.arrivalCode ?? '')
|
||||
const departureDate = computed(() => trip.value?.legs[0]?.segments[0]?.departureDate ?? '')
|
||||
|
||||
useSeoMeta({ title: () => `Vuelato - ${routeSummary.value || 'Detalle'}` })
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await getDetail(token.value, passengers.value)
|
||||
trip.value = data.trip
|
||||
} catch (e: any) {
|
||||
error.value = e?.data?.message || 'Error loading detail'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPageSection>
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<UButton label="Volver" icon="i-lucide-arrow-left" variant="ghost" class="mb-4" @click="$router.back()" />
|
||||
|
||||
<div v-if="loading" class="space-y-4">
|
||||
<USkeleton class="h-48 w-full" />
|
||||
<USkeleton class="h-48 w-full" />
|
||||
</div>
|
||||
|
||||
<UAlert v-else-if="error" color="error" icon="i-lucide-alert-circle" :title="error" />
|
||||
|
||||
<template v-else-if="trip">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">{{ routeSummary }}</h1>
|
||||
<p class="text-sm text-muted">
|
||||
{{ passengers.adult + passengers.child + passengers.infant }} pasajero(s)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="text-right">
|
||||
<p class="text-3xl font-bold text-primary-600 dark:text-primary-400">
|
||||
{{ originalPrice.toFixed(0) }}€
|
||||
</p>
|
||||
</div>
|
||||
<DetailWatchlistToggle
|
||||
:booking-token="token"
|
||||
:route-summary="routeSummary"
|
||||
:departure-code="departureCode"
|
||||
:arrival-code="arrivalCode"
|
||||
:departure-date="departureDate"
|
||||
:price="originalPrice"
|
||||
:passengers="passengers"
|
||||
/>
|
||||
<DetailShareButton :title="routeSummary" :price="originalPrice" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Itinerary -->
|
||||
<DetailItineraryTimeline :trip="trip" />
|
||||
|
||||
<!-- Price verifier -->
|
||||
<div class="mt-6">
|
||||
<DetailPriceVerifier
|
||||
:booking-token="token"
|
||||
:original-price="originalPrice"
|
||||
:passengers="passengers"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Booking CTA -->
|
||||
<div v-if="trip.deepLink" class="mt-4">
|
||||
<UButton
|
||||
:to="trip.deepLink"
|
||||
target="_blank"
|
||||
label="Reservar en aerolinea"
|
||||
icon="i-lucide-external-link"
|
||||
size="lg"
|
||||
block
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Related flights -->
|
||||
<div v-if="departureCode && arrivalCode" class="mt-6">
|
||||
<DetailRelatedFlights :from="departureCode" :to="arrivalCode" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</UPageSection>
|
||||
</template>
|
||||
Reference in New Issue
Block a user