import PageShell from '../components/Layout/PageShell'
import { useTranslation, TransHtml } from '../i18n'
import {
Plus, Search, Sparkles, Calendar, MapPin, BookOpen, Camera,
Check, X, ChevronRight, RefreshCw, Users,
} from 'lucide-react'
import type { Journey } from '../store/journeyStore'
import { computeJourneyLifecycle } from '../utils/journeyLifecycle'
import { useJourney } from './journey/useJourney'
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 { t } = useTranslation()
// Page = wiring container: store load, create modal, search + suggestions in the hook.
const {
navigate, journeys, loading,
showCreate, setShowCreate, newTitle, setNewTitle,
availableTrips, selectedTripIds, setSelectedTripIds,
searchOpen, setSearchOpen, searchQuery, setSearchQuery, searchInputRef,
activeSuggestion, setDismissedSuggestions,
activeJourney, filteredJourneys,
openCreateModal, handleCreate, totalPlaces,
} = useJourney()
return (
{/* Header โ mobile */}
{/* Header โ desktop (unified toolbar) */}
{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-[transform,box-shadow] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] 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).place_count ?? '--', label: t("journey.stats.places") },
].map(s => (
{s.val}
{s.label}
))}
{t('journey.frontpage.continueWriting')}
)}
{/* Search results info */}
{searchQuery.trim() && (
{filteredJourneys.length === 0
? t('journey.search.noResults', { query: searchQuery.trim() })
: `${filteredJourneys.length} ${t('journey.frontpage.journeys')}`}
)}
{/* All Journeys */}
{!searchQuery.trim() && (
{t("journey.frontpage.allJourneys")}
{journeys.length} {t('journey.frontpage.journeys')}
)}
{loading && journeys.length === 0 ? (
) : (
{filteredJourneys.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-[border-color,background-color] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] ${
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; place_count?: number; trip_date_min?: string | null; trip_date_max?: string | null }; onClick: () => void }) {
const { t } = useTranslation()
const j = journey
const entryCount = j.entry_count ?? 0
const photoCount = j.photo_count ?? 0
const placeCount = j.place_count ?? 0
const lifecycle = computeJourneyLifecycle(j.status, j.trip_date_min, j.trip_date_max)
return (
{/* Cover */}
{j.cover_image && (
<>
>
)}
{/* Top overlay */}
{new Date(j.created_at).getFullYear()}
{/* Body */}
{j.title}
{j.subtitle && (
{j.subtitle}
)}
{lifecycle !== 'live' && (
{t(`journey.status.${lifecycle}`)}
)}
{[
{ val: entryCount, label: t('journey.stats.entries') },
{ val: photoCount, label: t('journey.stats.photos') },
{ val: placeCount, label: t('journey.stats.places') },
].map(s => (
0 ? 'text-zinc-900 dark:text-white' : 'text-zinc-300 dark:text-zinc-600'}`}>
{s.val > 0 ? s.val : '--'}
{s.label}
))}
)
}