import { useEffect, useState, useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { useJourneyStore } from '../store/journeyStore' import { journeyApi } from '../api/client' import Navbar from '../components/Layout/Navbar' import { useToast } from '../components/shared/Toast' import { useTranslation } from '../i18n' import { Plus, Search, Sparkles, Calendar, MapPin, BookOpen, Camera, Check, X, ChevronRight, RefreshCw, Users, } from 'lucide-react' import type { Journey } from '../store/journeyStore' const GRADIENTS = [ 'linear-gradient(135deg, #0F172A 0%, #6366F1 45%, #EC4899 100%)', 'linear-gradient(135deg, #1E293B 0%, #7C3AED 50%, #F59E0B 100%)', 'linear-gradient(135deg, #134E5E 0%, #71B280 100%)', 'linear-gradient(135deg, #2D1B69 0%, #11998E 100%)', 'linear-gradient(135deg, #4B134F 0%, #C94B4B 100%)', 'linear-gradient(135deg, #373B44 0%, #4286F4 100%)', ] function pickGradient(id: number): string { return GRADIENTS[id % GRADIENTS.length] } function timeAgo(timestamp: number, t: (k: string, p?: any) => string): string { const diff = Date.now() - timestamp const hours = Math.floor(diff / 3600000) if (hours < 1) return t('common.justNow') if (hours < 24) return t('common.hoursAgo', { count: hours }) const days = Math.floor(hours / 24) return t('common.daysAgo', { count: days }) } export default function JourneyPage() { const navigate = useNavigate() const toast = useToast() const { t } = useTranslation() const { journeys, loading, loadJourneys, createJourney } = useJourneyStore() const [showCreate, setShowCreate] = useState(false) const [newTitle, setNewTitle] = useState('') const [availableTrips, setAvailableTrips] = useState([]) const [selectedTripIds, setSelectedTripIds] = useState>(new Set()) // suggestion const [suggestions, setSuggestions] = useState([]) const [dismissedSuggestions, setDismissedSuggestions] = useState>(new Set()) useEffect(() => { loadJourneys() journeyApi.suggestions().then(d => setSuggestions(d.trips || [])).catch(() => {}) }, []) const activeSuggestion = suggestions.find(s => !dismissedSuggestions.has(s.id)) const activeJourney = useMemo(() => { return journeys.find(j => j.status === 'active') || null }, [journeys]) const otherJourneys = useMemo(() => { return journeys.filter(j => j.id !== activeJourney?.id) }, [journeys, activeJourney]) const openCreateModal = async (preSelectedTripId?: number) => { setShowCreate(true) setNewTitle('') const initial = new Set() if (preSelectedTripId) initial.add(preSelectedTripId) setSelectedTripIds(initial) try { const data = await journeyApi.availableTrips() setAvailableTrips(data.trips || []) } catch {} } const handleCreate = async () => { if (!newTitle.trim()) return try { const j = await createJourney({ title: newTitle.trim(), trip_ids: [...selectedTripIds], }) setShowCreate(false) navigate(`/journey/${j.id}`) } catch { toast.error(t('journey.createError')) } } const totalPlaces = useMemo(() => { return availableTrips.filter(t => selectedTripIds.has(t.id)).reduce((sum: number, t: any) => sum + (t.place_count || 0), 0) }, [availableTrips, selectedTripIds]) return (
{/* Header โ€” mobile: just a create button */}
{/* Header โ€” desktop */}

{t('journey.title')}

{t("journey.frontpage.subtitle")}

{/* Suggestion banner */} {activeSuggestion && (
{t("journey.frontpage.suggestionLabel")}
)} {/* Active Journey Hero */} {activeJourney && (
{t("journey.frontpage.activeJourney")} {t('journey.frontpage.updated', { time: timeAgo(activeJourney.updated_at, t) })}
navigate(`/journey/${activeJourney.id}`)} className="relative rounded-3xl overflow-hidden cursor-pointer transition-all duration-300 hover:-translate-y-1 hover:shadow-xl h-[340px] md:h-[400px]" style={{ background: pickGradient(activeJourney.id) }} > {/* Cover image */} {activeJourney.cover_image && (
)} {/* Gradient overlays */}
{/* Top badges */}
{t('journey.frontpage.live')} {t('journey.frontpage.synced')}
{/* Middle โ€” title */}
{activeJourney.subtitle && (

{activeJourney.subtitle}

)}

{activeJourney.title}

{/* Bottom stats */}
{[ { val: (activeJourney as any).entry_count ?? '--', label: t("journey.stats.entries") }, { val: (activeJourney as any).photo_count ?? '--', label: t("journey.stats.photos") }, { val: (activeJourney as any).city_count ?? '--', label: t("journey.stats.cities") }, ].map(s => (
{s.val} {s.label}
))}
{t('journey.frontpage.continueWriting')}
)} {/* All Journeys */}
{t("journey.frontpage.allJourneys")} {journeys.length} {t('journey.frontpage.journeys')}
{loading && journeys.length === 0 ? (
) : (
{otherJourneys.map(j => ( navigate(`/journey/${j.id}`)} /> ))} {/* Create card */}
)}
{/* Create Modal */} {showCreate && (
{/* Header */}

{t("journey.frontpage.createJourney")}

{t('journey.frontpage.createNewSub')}

{/* Body */}
setNewTitle(e.target.value)} placeholder={t('journey.frontpage.namePlaceholder')} className="w-full px-3.5 py-2.5 border border-zinc-200 dark:border-zinc-700 rounded-lg text-[14px] bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white focus:border-zinc-900 dark:focus:border-zinc-400 focus:outline-none mb-5" />
{availableTrips.map(trip => { const selected = selectedTripIds.has(trip.id) const status = trip.end_date && trip.end_date < new Date().toISOString().split('T')[0] ? 'completed' : trip.start_date && trip.start_date <= new Date().toISOString().split('T')[0] ? 'active' : 'upcoming' const statusColors: Record = { completed: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400', active: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400', upcoming: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400', } return (
{ setSelectedTripIds(prev => { const next = new Set(prev) if (next.has(trip.id)) next.delete(trip.id) else next.add(trip.id) return next }) }} className={`flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-all ${ selected ? 'border-zinc-900 dark:border-zinc-400 bg-zinc-50 dark:bg-zinc-800' : 'border-zinc-200 dark:border-zinc-700 hover:border-zinc-400 dark:hover:border-zinc-500' }`} >
{selected && }
{trip.cover_image && ( )}
{trip.title}
{trip.start_date ? Math.ceil((new Date(trip.end_date || trip.start_date).getTime() - new Date(trip.start_date).getTime()) / 86400000) + 1 : '?'} {t('journey.stats.days').toLowerCase()} {trip.place_count || 0} {t("journey.frontpage.places")}
{t(`journey.status.${status}`)}
) })}
{/* Footer */}
{selectedTripIds.size} {t('journey.frontpage.tripsSelected')}{t('journey.frontpage.trips')} {selectedTripIds.size > 0 && <> ยท {totalPlaces} {t('journey.frontpage.placesImported')}{t('journey.frontpage.places')}}
)}
) } function JourneyCard({ journey, onClick }: { journey: Journey & { entry_count?: number; photo_count?: number; city_count?: number }; onClick: () => void }) { const { t } = useTranslation() const j = journey const entryCount = j.entry_count ?? 0 const photoCount = j.photo_count ?? 0 const cityCount = j.city_count ?? 0 return (
{/* Cover */}
{j.cover_image && ( <>
)}
{/* Top overlay */}
{new Date(j.created_at).getFullYear()}
{/* Body */}

{j.title}

{j.subtitle && (

{j.subtitle}

)} {j.status === 'draft' && ( {t('journey.status.draft')} )}
{[ { val: entryCount, label: t('journey.stats.entries') }, { val: photoCount, label: t('journey.stats.photos') }, { val: cityCount, label: t('journey.stats.cities') }, ].map(s => (
0 ? 'text-zinc-900 dark:text-white' : 'text-zinc-300 dark:text-zinc-600'}`}> {s.val > 0 ? s.val : '--'} {s.label}
))}
) }