Merge pull request #253 from trails-cool/feat/route-empty-state-cta

Show empty-state CTA on Route detail when no waypoints
This commit is contained in:
Ullrich Schäfer 2026-04-19 08:21:17 +02:00 committed by GitHub
commit 29cbcef844
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 1 deletions

View file

@ -99,6 +99,11 @@ export default function RouteDetailPage({ loaderData }: Route.ComponentProps) {
const [editLoading, setEditLoading] = useState(false);
const [highlightedDay, setHighlightedDay] = useState<number | null>(null);
// A route is "empty" when it has no geometry and no computed distance —
// i.e. nobody has planned any waypoints yet. Surfaced as a dedicated
// empty-state below so the page isn't a dead end.
const isEmpty = !route.geojson && route.distance == null;
const handleEditInPlanner = useCallback(async () => {
setEditLoading(true);
try {
@ -121,7 +126,7 @@ export default function RouteDetailPage({ loaderData }: Route.ComponentProps) {
<p className="mt-2 text-gray-600">{route.description}</p>
)}
</div>
{isOwner && (
{isOwner && !isEmpty && (
<div className="flex flex-wrap gap-2">
<button
onClick={handleEditInPlanner}
@ -222,6 +227,35 @@ export default function RouteDetailPage({ loaderData }: Route.ComponentProps) {
</div>
)}
{isEmpty && (
<div className="mt-6 flex flex-col items-center rounded-lg border border-dashed border-gray-300 bg-gray-50 px-6 py-12 text-center">
<div className="text-4xl" aria-hidden="true">🗺</div>
<h2 className="mt-4 text-lg font-semibold text-gray-900">
{t("routes.empty.heading")}
</h2>
<p className="mt-2 max-w-md text-sm text-gray-600">
{isOwner ? t("routes.empty.bodyOwner") : t("routes.empty.bodyViewer")}
</p>
{isOwner && (
<div className="mt-6 flex flex-wrap items-center justify-center gap-3">
<button
onClick={handleEditInPlanner}
disabled={editLoading}
className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
>
{editLoading ? t("routes.opening") : t("routes.empty.openInPlanner")}
</button>
<a
href={`/routes/${route.id}/edit`}
className="text-sm text-gray-600 underline hover:text-gray-900"
>
{t("routes.empty.uploadGpx")}
</a>
</div>
)}
</div>
)}
{versions.length > 0 && (
<div className="mt-8">
<h2 className="text-lg font-semibold text-gray-900">Version History</h2>

View file

@ -179,6 +179,13 @@ export default {
noRoutesYet: "Noch keine Routen. Erstelle deine erste Route!",
noMapPreview: "Keine Kartenvorschau",
saveChanges: "Änderungen speichern",
empty: {
heading: "Diese Route ist leer",
bodyOwner: "Du hast noch keine Wegpunkte geplant. Öffne sie im Planer, um eine Strecke zu skizzieren, oder lade eine GPX-Datei hoch.",
bodyViewer: "Diese Route enthält noch keine Wegpunkte.",
openInPlanner: "Im Planer öffnen",
uploadGpx: "oder eine GPX-Datei hochladen",
},
},
activities: {
title: "Aktivitäten",

View file

@ -179,6 +179,13 @@ export default {
noRoutesYet: "No routes yet. Create your first route!",
noMapPreview: "No map preview",
saveChanges: "Save Changes",
empty: {
heading: "This route is empty",
bodyOwner: "You haven't planned any waypoints yet. Open it in the Planner to sketch a path, or upload a GPX file.",
bodyViewer: "This route doesn't have any waypoints yet.",
openInPlanner: "Open in Planner",
uploadGpx: "or upload a GPX file",
},
},
activities: {
title: "Activities",