import type { AssistantFoundPlace } from '@trek/shared'; import { Check, MapPin, Plus, Route, Star } from 'lucide-react'; import { createElement, type ReactNode, useRef } from 'react'; import { useTranslation } from '../../i18n'; import { getCategoryIcon } from '../shared/categoryIcons'; import { Tooltip } from '../shared/Tooltip'; import { useAssistantView } from './assistantViewContext'; import type { useAssistantPlaceStrip } from './useAssistantPlaceStrip'; import { useFoundPlacePhoto } from './useFoundPlacePhoto'; type Strip = ReturnType; const ACTION = 'inline-flex items-center gap-1 rounded-lg bg-surface-secondary text-caption font-medium text-content transition-colors hover:bg-surface-hover disabled:cursor-default disabled:opacity-50 disabled:hover:bg-surface-secondary'; /** "Add to trip": opens the planner's place form filled in with the place, its details loading beside it. */ function AddButton({ place, strip, tall }: { place: AssistantFoundPlace; strip: Strip; tall: boolean }) { const { t } = useTranslation(); return ( ); } /** "+ Tour": takes the place into the saved tour open in the planner as a stop, held as a change in the chat. */ function TourButton({ place, strip, tall }: { place: AssistantFoundPlace; strip: Strip; tall: boolean }) { const { t } = useTranslation(); if (!strip.tour) return null; return ( ); } type Category = ReturnType; /** The picture, or the category's icon on its colour, with the category and whether it is open on it. */ function PlaceMedia({ place, photo, category, }: { place: AssistantFoundPlace; photo: string | null; category: Category; }) { const { t } = useTranslation(); const icon = category ? getCategoryIcon(category.icon) : MapPin; return ( {photo ? ( ) : ( {createElement(icon, { size: 30, 'aria-hidden': true, style: { color: category?.color ?? 'var(--text-faint)' }, })} )} {category && photo && ( {createElement(icon, { size: 11, 'aria-hidden': true, className: 'flex-shrink-0', style: { color: category.color }, })} {category.name} )} {place.openNow !== null && ( )} ); } /** The card's one way on: "On map" for a place the trip has, "Suggested" once it is, else "+ Day 3". */ function PlaceAction({ place, strip, suggested, tall, }: { place: AssistantFoundPlace; strip: Strip; suggested: boolean; tall: boolean; }): ReactNode { const { t } = useTranslation(); if (place.inTripPlaceId !== null) { return ( ); } if (suggested) { return ( ); } return strip.canAdd ? : null; } /** * One place a search found, as a card: its picture from TREK's photo proxy * with the category it most likely belongs in on it (or that category's icon * on its colour), its name, address and rating, whether it is open now while * the trip runs, how far it is from the last stop of the day in focus, and one * way on: "Add to trip" for a new place, "On map" for one the trip has. A click * on a new place opens the place form filled in with it (one the trip has, or * any where nothing can be added, shows on the map), and resting the pointer on * it pins it there for a moment. */ export default function AssistantPlaceCard({ place, strip, suggested, }: { place: AssistantFoundPlace; strip: Strip; suggested: boolean; }) { const { t } = useTranslation(); const { variant, density } = useAssistantView(); const ref = useRef(null); const photo = useFoundPlacePhoto(place, ref); const category = strip.categoryOf(place); const distance = strip.distanceOf(place); const tall = variant === 'fullscreen'; return (
  • strip.hover(place)} onMouseLeave={() => strip.hover(null)} >
  • ); }