Add Google Calendar integration and improve station selection

Implement full OAuth 2.0 flow for Google Calendar, including token
exchange, refresh, and status checks. Add UI for connecting, syncing,
and disconnecting Google accounts in the Calendar panel.

Additionally:
- Make mobile station selection async with error handling and alerts
- Add HAFAS LocMatch method to API client for finding nearest station
- Expose Google OAuth env vars in Next.js config
This commit is contained in:
2026-05-13 09:02:58 +02:00
parent 672d053ece
commit de9a8606ab
10 changed files with 592 additions and 35 deletions
+51
View File
@@ -178,4 +178,55 @@ export class ApiClient {
});
return result?.svcReqL?.[0]?.res?.locL ?? [];
}
/**
* Find the nearest station to given GPS coordinates using HAFAS LocMatch.
* This is a reliable fallback that works even when the nearby-stops proxy
* is unavailable or the base URL is empty.
*/
async findNearestStationByCoords(lat: number, lng: number): Promise<Station | null> {
interface HafasLocation extends Station {
type: string;
lon: number;
}
const result = await this.hafasRequest<{
svcReqL?: Array<{ res?: { match?: { locL?: HafasLocation[] } } }>;
}>({
svcReqL: [
{
meth: "LocMatch",
req: {
input: {
loc: {
crd: {
x: Math.round(lng * 1e6),
y: Math.round(lat * 1e6),
},
type: "S",
},
maxLoc: 5,
field: "S",
},
},
},
],
});
const stations = result?.svcReqL?.[0]?.res?.match?.locL ?? [];
if (stations.length === 0) return null;
// Filter to only "S" (station) type results, then pick the closest
const stationResults = stations.filter((s) => s.type === "S");
if (stationResults.length === 0) return null;
// Find the closest station by Euclidean distance
const closest = stationResults.reduce((best, candidate) => {
const bestDist = Math.hypot((best.lat ?? lat) - lat, (best.lng ?? lng) - lng);
const candDist = Math.hypot((candidate.lat ?? lat) - lat, (candidate.lng ?? lng) - lng);
return candDist < bestDist ? candidate : best;
}, stationResults[0]);
return closest;
}
}