132 lines
4.9 KiB
TypeScript
132 lines
4.9 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { AppSwitcher } from '@/components/AppSwitcher';
|
|
import { format } from 'date-fns';
|
|
import { apiUrl } from '@/lib/api';
|
|
|
|
interface TripSummary {
|
|
id: string;
|
|
title: string;
|
|
slug: string;
|
|
description: string | null;
|
|
startDate: string | null;
|
|
endDate: string | null;
|
|
budgetTotal: number | null;
|
|
budgetCurrency: string;
|
|
status: string;
|
|
destinations: { name: string; country: string | null }[];
|
|
_count: {
|
|
itineraryItems: number;
|
|
bookings: number;
|
|
expenses: number;
|
|
packingItems: number;
|
|
};
|
|
updatedAt: string;
|
|
}
|
|
|
|
const STATUS_COLORS: Record<string, string> = {
|
|
PLANNING: 'bg-teal-500/20 text-teal-300',
|
|
BOOKED: 'bg-blue-500/20 text-blue-300',
|
|
IN_PROGRESS: 'bg-amber-500/20 text-amber-300',
|
|
COMPLETED: 'bg-emerald-500/20 text-emerald-300',
|
|
CANCELLED: 'bg-slate-500/20 text-slate-400',
|
|
};
|
|
|
|
export default function TripsPage() {
|
|
const [trips, setTrips] = useState<TripSummary[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
fetch(apiUrl('/api/trips'))
|
|
.then((res) => res.json())
|
|
.then(setTrips)
|
|
.catch(console.error)
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white">
|
|
{/* Nav */}
|
|
<nav className="border-b border-slate-700/50 backdrop-blur-sm">
|
|
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<AppSwitcher current="trips" />
|
|
<Link href="/" className="flex items-center gap-2">
|
|
<div className="w-8 h-8 bg-gradient-to-br from-teal-400 to-cyan-500 rounded-lg flex items-center justify-center font-bold text-slate-900 text-sm">
|
|
rT
|
|
</div>
|
|
<span className="font-semibold text-lg">rTrips</span>
|
|
</Link>
|
|
</div>
|
|
<Link
|
|
href="/trips/new"
|
|
className="text-sm px-4 py-2 bg-teal-600 hover:bg-teal-500 rounded-lg transition-colors font-medium"
|
|
>
|
|
Plan a Trip
|
|
</Link>
|
|
</div>
|
|
</nav>
|
|
|
|
<div className="max-w-4xl mx-auto px-6 py-12">
|
|
<h1 className="text-3xl font-bold mb-8">My Trips</h1>
|
|
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-20">
|
|
<svg className="animate-spin h-8 w-8 text-teal-400" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
</svg>
|
|
</div>
|
|
) : trips.length === 0 ? (
|
|
<div className="text-center py-20">
|
|
<p className="text-slate-400 mb-4">No trips yet. Start planning your first adventure!</p>
|
|
<Link
|
|
href="/trips/new"
|
|
className="inline-block px-6 py-3 bg-teal-600 hover:bg-teal-500 rounded-xl font-medium transition-all"
|
|
>
|
|
Plan a Trip
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{trips.map((trip) => (
|
|
<Link
|
|
key={trip.id}
|
|
href={`/trips/${trip.id}`}
|
|
className="block bg-slate-800/50 rounded-xl p-5 border border-slate-700/50 hover:border-teal-500/30 transition-all group"
|
|
>
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div>
|
|
<h2 className="text-lg font-semibold group-hover:text-teal-300 transition-colors">{trip.title}</h2>
|
|
{trip.destinations.length > 0 && (
|
|
<p className="text-sm text-slate-400 mt-0.5">
|
|
{trip.destinations.map((d) => d.name).join(' → ')}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<span className={`text-xs px-2 py-1 rounded-full ${STATUS_COLORS[trip.status] || STATUS_COLORS.PLANNING}`}>
|
|
{trip.status.replace('_', ' ')}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4 text-xs text-slate-500">
|
|
{trip.startDate && (
|
|
<span>{format(new Date(trip.startDate), 'MMM d, yyyy')}</span>
|
|
)}
|
|
{trip.budgetTotal && (
|
|
<span>{trip.budgetCurrency} {trip.budgetTotal.toLocaleString()}</span>
|
|
)}
|
|
<span>{trip._count.itineraryItems} items</span>
|
|
<span>{trip._count.bookings} bookings</span>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|