Initial commit: Vuelato - buscador de vuelos
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:
Alejandro Martinez
2026-04-10 23:37:06 +02:00
commit b8906efc80
122 changed files with 37809 additions and 0 deletions

View 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) }}&euro;
</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>