Fix GPX export: use API route with direct download link
React Router page actions wrap responses, preventing raw file downloads. Moved GPX export to GET /api/routes/:id/gpx and changed the button to a plain <a download> link. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
155d36e94c
commit
2e2dbe05c1
3 changed files with 24 additions and 20 deletions
|
|
@ -14,5 +14,6 @@ export default [
|
||||||
route("routes/:id/edit", "routes/routes.$id.edit.tsx"),
|
route("routes/:id/edit", "routes/routes.$id.edit.tsx"),
|
||||||
route("api/routes/:id/callback", "routes/api.routes.$id.callback.ts"),
|
route("api/routes/:id/callback", "routes/api.routes.$id.callback.ts"),
|
||||||
route("api/routes/:id/edit-in-planner", "routes/api.routes.$id.edit-in-planner.ts"),
|
route("api/routes/:id/edit-in-planner", "routes/api.routes.$id.edit-in-planner.ts"),
|
||||||
|
route("api/routes/:id/gpx", "routes/api.routes.$id.gpx.ts"),
|
||||||
route("users/:username", "routes/users.$username.tsx"),
|
route("users/:username", "routes/users.$username.tsx"),
|
||||||
] satisfies RouteConfig;
|
] satisfies RouteConfig;
|
||||||
|
|
|
||||||
16
apps/journal/app/routes/api.routes.$id.gpx.ts
Normal file
16
apps/journal/app/routes/api.routes.$id.gpx.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { Route } from "./+types/api.routes.$id.gpx";
|
||||||
|
import { getRouteWithVersions } from "~/lib/routes.server";
|
||||||
|
|
||||||
|
export async function loader({ params }: Route.LoaderArgs) {
|
||||||
|
const route = await getRouteWithVersions(params.id);
|
||||||
|
if (!route?.gpx) {
|
||||||
|
return new Response("No GPX data", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(route.gpx, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/gpx+xml",
|
||||||
|
"Content-Disposition": `attachment; filename="${route.name.replace(/[^a-z0-9]/gi, "_")}.gpx"`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -62,18 +62,6 @@ export async function action({ params, request }: Route.ActionArgs) {
|
||||||
return redirect(`/routes/${params.id}`);
|
return redirect(`/routes/${params.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent === "export-gpx") {
|
|
||||||
const route = await getRouteWithVersions(params.id);
|
|
||||||
if (!route?.gpx) return data({ error: "No GPX data" }, { status: 400 });
|
|
||||||
|
|
||||||
return new Response(route.gpx, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/gpx+xml",
|
|
||||||
"Content-Disposition": `attachment; filename="${route.name.replace(/[^a-z0-9]/gi, "_")}.gpx"`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return data({ error: "Unknown action" }, { status: 400 });
|
return data({ error: "Unknown action" }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,16 +111,15 @@ export default function RouteDetailPage({ loaderData }: Route.ComponentProps) {
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</a>
|
||||||
<form method="post">
|
{route.hasGpx && (
|
||||||
<input type="hidden" name="intent" value="export-gpx" />
|
<a
|
||||||
<button
|
href={`/api/routes/${route.id}/gpx`}
|
||||||
type="submit"
|
download
|
||||||
disabled={!route.hasGpx}
|
className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
|
||||||
className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:opacity-50"
|
|
||||||
>
|
>
|
||||||
Export GPX
|
Export GPX
|
||||||
</button>
|
</a>
|
||||||
</form>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue