import React, { useEffect, useMemo, useState, useCallback } from 'react' import { useNavigate } from 'react-router-dom' import { Bookmark, BookmarkCheck, Check, Loader2, Plus } from 'lucide-react' import Modal from '../shared/Modal' import { useToast } from '../shared/Toast' import { useTranslation } from '../../i18n' import { collectionsApi } from '../../api/collections' import { useSaveToCollectionStore } from '../../store/saveToCollectionStore' import { getApiErrorMessage } from '../../utils/apiError' import type { Collection, CollectionMembership } from '@trek/shared' /** * Globally-mounted list picker for the "Save to Collection" entry points * (PlaceInspector footer button + the two trip-sidebar context menus). Reads the * active target from saveToCollectionStore, shows every list the user owns or * co-owns, and toggles the place in/out of each — a check marks the lists that * already hold it. Each change refreshes membership and bumps the store version * so the inspector bookmark indicator stays in sync. One mount, no prop drilling. */ export default function SaveToCollectionModal(): React.ReactElement | null { const target = useSaveToCollectionStore(s => s.target) const close = useSaveToCollectionStore(s => s.close) const bumpVersion = useSaveToCollectionStore(s => s.bumpVersion) const { t } = useTranslation() const toast = useToast() const navigate = useNavigate() const [lists, setLists] = useState([]) const [membership, setMembership] = useState(null) const [loading, setLoading] = useState(false) const [busyId, setBusyId] = useState(null) const membershipQuery = useMemo(() => { if (!target) return null return { google_place_id: target.google_place_id ?? undefined, google_ftid: target.google_ftid ?? undefined, name: target.name, lat: target.lat ?? undefined, lng: target.lng ?? undefined, } }, [target]) const refreshMembership = useCallback(async () => { if (!membershipQuery) return try { const m = await collectionsApi.membership(membershipQuery) setMembership(m) } catch { setMembership({ saved: false, lists: [] }) } }, [membershipQuery]) // Load lists + membership whenever the picker opens for a new target. useEffect(() => { if (!target) return let cancelled = false setLoading(true) setMembership(null) Promise.all([collectionsApi.list().catch(() => ({ collections: [], incomingInvites: [] })), membershipQuery ? collectionsApi.membership(membershipQuery).catch(() => ({ saved: false, lists: [] as CollectionMembership['lists'] })) : Promise.resolve({ saved: false, lists: [] as CollectionMembership['lists'] })]) .then(([listRes, m]) => { if (cancelled) return setLists(listRes.collections) setMembership(m) }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } // eslint-disable-next-line react-hooks/exhaustive-deps }, [target]) if (!target) return null const savedByCollection = new Map() for (const l of membership?.lists ?? []) savedByCollection.set(l.collection_id, l.place_id) const handleToggle = async (list: Collection) => { if (busyId != null) return const savedPlaceId = savedByCollection.get(list.id) setBusyId(list.id) try { if (savedPlaceId != null) { await collectionsApi.deletePlace(savedPlaceId) toast.success(t('collections.removedFromList', { name: list.name })) } else { await collectionsApi.savePlace({ collection_id: list.id, source_trip_id: target.source_trip_id ?? null, source_place_id: target.source_place_id ?? null, name: target.name, description: target.description ?? null, lat: target.lat ?? null, lng: target.lng ?? null, address: target.address ?? null, category_id: target.category_id ?? null, price: target.price ?? null, currency: target.currency ?? null, notes: target.notes ?? null, image_url: target.image_url ?? null, google_place_id: target.google_place_id ?? null, google_ftid: target.google_ftid ?? null, osm_id: target.osm_id ?? null, website: target.website ?? null, phone: target.phone ?? null, force: true, }) toast.success(t('collections.addedToList', { name: list.name })) } await refreshMembership() bumpVersion() } catch (err) { toast.error(getApiErrorMessage(err, t('common.error'))) } finally { setBusyId(null) } } return ( } >

{target.name}

{loading ? (
) : lists.length === 0 ? (

{t('collections.noListsYet')}

) : (
{lists.map(list => { const saved = savedByCollection.has(list.id) const busy = busyId === list.id return ( ) })}
)}
) }