mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-05-31 08:33:36 +00:00
chore: airline basic refactor
This commit is contained in:
@@ -1,73 +1,65 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect, Suspense } from 'react';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { Navigation } from '@/components/ui';
|
import { Navigation } from '@/components/ui';
|
||||||
import AirlineCard from '@/components/feats/airline/AirlineCard';
|
import AirlineCard from '@/components/feats/airline/AirlineCard';
|
||||||
|
import { transformProduct, type Flight, type AirlineProduct } from '@/lib/airline-utils';
|
||||||
|
|
||||||
type CabinClass = 'economy' | 'premium' | 'business' | 'first';
|
function FlightsList() {
|
||||||
type FareRule = 'flexible' | 'standard' | 'basic';
|
const searchParams = useSearchParams();
|
||||||
|
const [flights, setFlights] = useState<Flight[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
interface Flight {
|
useEffect(() => {
|
||||||
id: string;
|
const fetchFlights = async () => {
|
||||||
departure: { time: string; airport: string };
|
try {
|
||||||
arrival: { time: string; airport: string };
|
const url = new URL('/api/products', window.location.origin);
|
||||||
duration: string;
|
url.searchParams.set('type', 'airline');
|
||||||
stops: number;
|
|
||||||
cabinClass: CabinClass;
|
|
||||||
fareRule: FareRule;
|
|
||||||
refundable: boolean;
|
|
||||||
basePrice: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const genRandomFlights = (): Flight[] => {
|
const dateIndex = searchParams.get('dateIndex');
|
||||||
const airports = ['JFK', 'LAX', 'ORD', 'ATL', 'DFW', 'SFO', 'SEA', 'MIA'];
|
if (dateIndex) url.searchParams.set('dateIndex', dateIndex);
|
||||||
const cabins: CabinClass[] = ['economy', 'premium', 'business', 'first'];
|
|
||||||
const fareRules: FareRule[] = ['flexible', 'standard', 'basic'];
|
|
||||||
|
|
||||||
return Array.from({ length: 12 }, (_, i) => {
|
const res = await fetch(url.toString());
|
||||||
const depHour = Math.floor(Math.random() * 24);
|
if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);
|
||||||
const arrHour = (depHour + Math.floor(Math.random() * 6) + 2) % 24;
|
const json = await res.json();
|
||||||
const stops = Math.random() > 0.6 ? 0 : Math.floor(Math.random() * 2) + 1;
|
const transformed = json.data.map((p: AirlineProduct) => transformProduct(p));
|
||||||
const cabin = cabins[Math.floor(Math.random() * cabins.length)];
|
setFlights(transformed);
|
||||||
const fareRule = fareRules[Math.floor(Math.random() * fareRules.length)];
|
} catch (e) {
|
||||||
|
setError(e instanceof Error ? e.message : 'Failed to load products');
|
||||||
const basePrice = Math.floor(
|
console.error('[FETCH_ERROR]', e);
|
||||||
(cabin === 'economy' ? 200 : cabin === 'premium' ? 400 : cabin === 'business' ? 800 : 1500) +
|
} finally {
|
||||||
Math.random() * 300
|
setLoading(false);
|
||||||
);
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
id: `flt-${i}`,
|
|
||||||
departure: {
|
|
||||||
time: `${depHour.toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}`,
|
|
||||||
airport: airports[Math.floor(Math.random() * airports.length)],
|
|
||||||
},
|
|
||||||
arrival: {
|
|
||||||
time: `${arrHour.toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}`,
|
|
||||||
airport: airports[Math.floor(Math.random() * airports.length)],
|
|
||||||
},
|
|
||||||
duration: `${Math.floor(Math.random() * 5) + 2}h ${Math.floor(Math.random() * 60)}m`,
|
|
||||||
stops,
|
|
||||||
cabinClass: cabin,
|
|
||||||
fareRule,
|
|
||||||
refundable: Math.random() > 0.7,
|
|
||||||
basePrice,
|
|
||||||
};
|
};
|
||||||
});
|
fetchFlights();
|
||||||
};
|
}, [searchParams]);
|
||||||
|
|
||||||
export default function AirlineProducts() {
|
|
||||||
const flights = genRandomFlights();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navigation />
|
|
||||||
<main className="max-w-7xl mx-auto px-4 py-8">
|
|
||||||
<h1 className="text-3xl font-bold mb-6">Available Flights</h1>
|
<h1 className="text-3xl font-bold mb-6">Available Flights</h1>
|
||||||
|
{loading && <div className="text-center py-8">Loading...</div>}
|
||||||
|
{error && <div className="text-red-500 text-center py-8">{error}</div>}
|
||||||
|
{!loading && !error && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{flights.map((f) => (
|
{flights.map((f) => (
|
||||||
<AirlineCard key={f.id} flight={f} />
|
<AirlineCard key={f.id} flight={f} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AirlineProducts() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navigation />
|
||||||
|
<main className="max-w-7xl mx-auto px-4 py-8">
|
||||||
|
<Suspense fallback={<div className="text-center py-8">Loading...</div>}>
|
||||||
|
<FlightsList />
|
||||||
|
</Suspense>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import type { EventName } from '@/lib/events';
|
import type { EventName } from '@/lib/events';
|
||||||
|
import type { Flight } from '@/lib/airline-utils';
|
||||||
import { useHoverTracking } from '@/hooks/useHoverTracking';
|
import { useHoverTracking } from '@/hooks/useHoverTracking';
|
||||||
import PriceDisplay from '@/components/ui/PriceDisplay';
|
import PriceDisplay from '@/components/ui/PriceDisplay';
|
||||||
|
|
||||||
@@ -11,22 +12,6 @@ const dispatchInteraction = (eventName: EventName, productId?: string, metadata?
|
|||||||
document.dispatchEvent(e);
|
document.dispatchEvent(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
type CabinClass = 'economy' | 'premium' | 'business' | 'first';
|
|
||||||
type FareRule = 'flexible' | 'standard' | 'basic';
|
|
||||||
|
|
||||||
interface Flight {
|
|
||||||
id: string;
|
|
||||||
departure: { time: string; airport: string };
|
|
||||||
arrival: { time: string; airport: string };
|
|
||||||
duration: string;
|
|
||||||
stops: number;
|
|
||||||
cabinClass: CabinClass;
|
|
||||||
fareRule: FareRule;
|
|
||||||
refundable: boolean;
|
|
||||||
basePrice: number;
|
|
||||||
dateIndex?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AirlineCard({ flight }: { flight: Flight }) {
|
export default function AirlineCard({ flight }: { flight: Flight }) {
|
||||||
const durationRef = useHoverTracking({
|
const durationRef = useHoverTracking({
|
||||||
eventName: 'hover_over_title',
|
eventName: 'hover_over_title',
|
||||||
|
|||||||
75
web/src/lib/airline-utils.ts
Normal file
75
web/src/lib/airline-utils.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
export interface AirlineProduct {
|
||||||
|
id: string;
|
||||||
|
flight_type: string;
|
||||||
|
date_index: number;
|
||||||
|
metadata: {
|
||||||
|
departure: { time: string; airport: string };
|
||||||
|
arrival: { time: string; airport: string };
|
||||||
|
duration: string;
|
||||||
|
stops: number;
|
||||||
|
cabin_class: string;
|
||||||
|
fare_rule: string;
|
||||||
|
refundable: boolean;
|
||||||
|
total?: number;
|
||||||
|
base_price: number;
|
||||||
|
};
|
||||||
|
availability: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Flight {
|
||||||
|
id: string;
|
||||||
|
flightType: string;
|
||||||
|
departure: { time: string; airport: string };
|
||||||
|
arrival: { time: string; airport: string };
|
||||||
|
duration: string;
|
||||||
|
stops: number;
|
||||||
|
cabinClass: string;
|
||||||
|
fareRule: string;
|
||||||
|
refundable: boolean;
|
||||||
|
basePrice: number;
|
||||||
|
dateIndex: number;
|
||||||
|
availability: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EPOCH = new Date(0);
|
||||||
|
|
||||||
|
export const transformProduct = (p: AirlineProduct): Flight => {
|
||||||
|
const { id, flight_type, date_index, metadata, availability } = p;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
flightType: flight_type,
|
||||||
|
departure: metadata.departure,
|
||||||
|
arrival: metadata.arrival,
|
||||||
|
duration: metadata.duration,
|
||||||
|
stops: metadata.stops,
|
||||||
|
cabinClass: metadata.cabin_class,
|
||||||
|
fareRule: metadata.fare_rule,
|
||||||
|
refundable: metadata.refundable,
|
||||||
|
basePrice: metadata.base_price,
|
||||||
|
dateIndex: date_index,
|
||||||
|
availability,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert date string to days from today
|
||||||
|
export const dateToDaysFromToday = (dateStr: string): number => {
|
||||||
|
const target = new Date(dateStr);
|
||||||
|
target.setHours(0, 0, 0, 0);
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
return Math.floor((target.getTime() - today.getTime()) / 86400000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert date string to date_index (days since epoch)
|
||||||
|
export const dateToIndex = (dateStr: string): number => {
|
||||||
|
const d = new Date(dateStr);
|
||||||
|
return Math.floor((d.getTime() - EPOCH.getTime()) / 86400000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// get current date_index
|
||||||
|
export const todayIndex = (): number => {
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(0, 0, 0, 0);
|
||||||
|
return Math.floor((now.getTime() - EPOCH.getTime()) / 86400000);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user