95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { AppSwitcher } from '@/components/AppSwitcher';
|
|
import { NLInput } from '@/components/NLInput';
|
|
import { ParsedTripPreview } from '@/components/ParsedTripPreview';
|
|
import { ParsedTrip } from '@/lib/types';
|
|
|
|
export default function NewTrip() {
|
|
const router = useRouter();
|
|
const [parsed, setParsed] = useState<ParsedTrip | null>(null);
|
|
const [rawInput, setRawInput] = useState('');
|
|
const [creating, setCreating] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const handleParsed = (data: ParsedTrip, raw: string) => {
|
|
setParsed(data);
|
|
setRawInput(raw);
|
|
};
|
|
|
|
const handleConfirm = async (finalParsed: ParsedTrip) => {
|
|
setCreating(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const res = await fetch('/api/trips', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ parsed: finalParsed, rawInput }),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const data = await res.json();
|
|
throw new Error(data.error || 'Failed to create trip');
|
|
}
|
|
|
|
const trip = await res.json();
|
|
router.push(`/trips/${trip.id}`);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Something went wrong');
|
|
setCreating(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" className="text-sm text-slate-300 hover:text-white transition-colors">
|
|
My Trips
|
|
</Link>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Content */}
|
|
<div className="max-w-2xl mx-auto px-6 py-12">
|
|
<h1 className="text-3xl font-bold mb-2">Plan a New Trip</h1>
|
|
<p className="text-slate-400 mb-8">
|
|
Describe your trip in natural language and we'll structure it for you.
|
|
</p>
|
|
|
|
{error && (
|
|
<div className="mb-6 p-3 bg-red-900/30 border border-red-700/50 rounded-lg text-red-300 text-sm">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{!parsed ? (
|
|
<NLInput onParsed={handleParsed} />
|
|
) : (
|
|
<ParsedTripPreview
|
|
parsed={parsed}
|
|
rawInput={rawInput}
|
|
onConfirm={handleConfirm}
|
|
onBack={() => setParsed(null)}
|
|
loading={creating}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|