fix: show pendingDestination as visible card when Get Directions clicked

When GPS is denied and user clicks Get Directions, StopList now shows:
- Empty origin slot (A) with dashed border and "Search for a starting point" prompt
- Destination slot (B) showing the place name from pendingDestination

Previously only showed small grey text, making it feel like the UI
showed "nothing" after the place card disappeared.
This commit is contained in:
Matt 2026-04-27 00:34:27 +00:00
commit 616d01623d

View file

@ -1,117 +1,158 @@
import { useState } from 'react' import { useState } from 'react'
import { import {
DndContext, DndContext,
closestCenter, closestCenter,
KeyboardSensor, KeyboardSensor,
PointerSensor, PointerSensor,
useSensor, useSensor,
useSensors, useSensors,
} from '@dnd-kit/core' } from '@dnd-kit/core'
import { import {
arrayMove, arrayMove,
SortableContext, SortableContext,
sortableKeyboardCoordinates, sortableKeyboardCoordinates,
verticalListSortingStrategy, verticalListSortingStrategy,
useSortable, useSortable,
} from '@dnd-kit/sortable' } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities' import { CSS } from '@dnd-kit/utilities'
import { useStore } from '../store' import { useStore } from '../store'
import { PlaceCard } from './PlaceCard' import { PlaceCard } from './PlaceCard'
import GpsOriginItem from './GpsOriginItem' import GpsOriginItem from './GpsOriginItem'
// Wrapper to make PlaceCard sortable // Wrapper to make PlaceCard sortable
function SortableStopCard({ stop, index, indexOffset }) { function SortableStopCard({ stop, index, indexOffset }) {
const removeStop = useStore((s) => s.removeStop) const removeStop = useStore((s) => s.removeStop)
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false)
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
useSortable({ id: stop.id }) useSortable({ id: stop.id })
const style = { const style = {
transform: CSS.Transform.toString(transform), transform: CSS.Transform.toString(transform),
transition, transition,
opacity: isDragging ? 0.5 : 1, opacity: isDragging ? 0.5 : 1,
} }
// Convert stop to place format for PlaceCard // Convert stop to place format for PlaceCard
const place = { const place = {
lat: stop.lat, lat: stop.lat,
lon: stop.lon, lon: stop.lon,
name: stop.name, name: stop.name,
source: stop.source, source: stop.source,
matchCode: stop.matchCode, matchCode: stop.matchCode,
type: stop.type || null, type: stop.type || null,
raw: stop.raw || null, raw: stop.raw || null,
wikidata: stop.wikidata || null, wikidata: stop.wikidata || null,
} }
return ( return (
<div ref={setNodeRef} style={style}> <div ref={setNodeRef} style={style}>
<PlaceCard <PlaceCard
place={place} place={place}
variant="stop" variant="stop"
expanded={expanded} expanded={expanded}
onToggleExpand={() => setExpanded(!expanded)} onToggleExpand={() => setExpanded(!expanded)}
onRemove={() => removeStop(stop.id)} onRemove={() => removeStop(stop.id)}
stopIndex={index + indexOffset} stopIndex={index + indexOffset}
draggable={true} draggable={true}
dragHandleProps={{ ...attributes, ...listeners }} dragHandleProps={{ ...attributes, ...listeners }}
/> />
</div> </div>
) )
} }
export default function StopList() { export default function StopList() {
const stops = useStore((s) => s.stops) const stops = useStore((s) => s.stops)
const reorderStops = useStore((s) => s.reorderStops) const reorderStops = useStore((s) => s.reorderStops)
const geoPermission = useStore((s) => s.geoPermission) const geoPermission = useStore((s) => s.geoPermission)
const gpsOrigin = useStore((s) => s.gpsOrigin) const gpsOrigin = useStore((s) => s.gpsOrigin)
const pendingDestination = useStore((s) => s.pendingDestination) const pendingDestination = useStore((s) => s.pendingDestination)
const hasGpsOrigin = gpsOrigin && geoPermission === 'granted' const hasGpsOrigin = gpsOrigin && geoPermission === 'granted'
const indexOffset = hasGpsOrigin ? 1 : 0 const indexOffset = hasGpsOrigin ? 1 : 0
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
) )
function handleDragEnd(event) { function handleDragEnd(event) {
const { active, over } = event const { active, over } = event
if (!over || active.id === over.id) return if (!over || active.id === over.id) return
const oldIndex = stops.findIndex((s) => s.id === active.id) const oldIndex = stops.findIndex((s) => s.id === active.id)
const newIndex = stops.findIndex((s) => s.id === over.id) const newIndex = stops.findIndex((s) => s.id === over.id)
reorderStops(arrayMove(stops, oldIndex, newIndex)) reorderStops(arrayMove(stops, oldIndex, newIndex))
} }
if (stops.length === 0 && !hasGpsOrigin) { // When pendingDestination is set, show it as the destination with origin prompt
return ( if (stops.length === 0 && !hasGpsOrigin && pendingDestination) {
<div className="text-xs px-2 py-3 text-center" style={{ color: 'var(--text-tertiary)' }}> return (
{pendingDestination <div className="flex flex-col gap-2">
? 'Search for a starting point above' {/* Origin slot - empty, prompting user to search */}
: geoPermission === 'denied' <div
? 'Add a starting point and destination above' className="flex items-center gap-2 px-3 py-2.5 rounded-lg text-xs"
: 'Search and add stops to build your route'} style={{
</div> background: 'var(--bg-muted)',
) border: '1px dashed var(--border)',
} color: 'var(--text-secondary)'
}}
return ( >
<div className="flex flex-col gap-2"> <span
{hasGpsOrigin && <GpsOriginItem />} className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold"
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> style={{ background: 'var(--accent-muted)', color: 'var(--accent)' }}
<SortableContext items={stops.map((s) => s.id)} strategy={verticalListSortingStrategy}> >
{stops.map((stop, i) => ( A
<SortableStopCard </span>
key={stop.id} <span className="flex-1">Search for a starting point above</span>
stop={stop} </div>
index={i} {/* Destination - show pendingDestination as read-only card */}
indexOffset={indexOffset} <div
/> className="flex items-center gap-2 px-3 py-2.5 rounded-lg text-xs"
))} style={{
</SortableContext> background: 'var(--bg-raised)',
</DndContext> border: '1px solid var(--border)'
</div> }}
) >
} <span
className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold"
style={{ background: 'var(--accent)', color: 'var(--text-inverse)' }}
>
B
</span>
<span className="flex-1 truncate" style={{ color: 'var(--text-primary)' }}>
{pendingDestination.name || 'Destination'}
</span>
</div>
</div>
)
}
if (stops.length === 0 && !hasGpsOrigin) {
return (
<div className="text-xs px-2 py-3 text-center" style={{ color: 'var(--text-tertiary)' }}>
{geoPermission === 'denied'
? 'Add a starting point and destination above'
: 'Search and add stops to build your route'}
</div>
)
}
return (
<div className="flex flex-col gap-2">
{hasGpsOrigin && <GpsOriginItem />}
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={stops.map((s) => s.id)} strategy={verticalListSortingStrategy}>
{stops.map((stop, i) => (
<SortableStopCard
key={stop.id}
stop={stop}
index={i}
indexOffset={indexOffset}
/>
))}
</SortableContext>
</DndContext>
</div>
)
}