From 86ee8044da01efb5c3bcf751f15c9a4bbe8bec41 Mon Sep 17 00:00:00 2001 From: "Julien G." <66769052+jubnl@users.noreply.github.com> Date: Mon, 25 May 2026 01:13:20 +0200 Subject: [PATCH] v3.0.22 Bug Fixes & Improvements (#1041) Bundles the v3.0.22 bug fixes and improvements. See the release notes for the full list. --- README.md | 2 +- client/src/api/client.ts | 18 ++- .../components/Journey/MobileEntryView.tsx | 2 +- client/src/components/Map/MapViewGL.tsx | 31 ++++ client/src/components/PDF/TripPDF.tsx | 3 +- .../src/components/Planner/DayDetailPanel.tsx | 29 ++-- .../src/components/Planner/DayPlanSidebar.tsx | 79 ++++++---- .../src/components/Planner/PlaceInspector.tsx | 41 ++++-- .../Planner/ReservationsPanel.test.tsx | 47 ++++++ .../components/Planner/ReservationsPanel.tsx | 55 +++---- .../src/components/Planner/TransportModal.tsx | 8 +- .../components/Settings/IntegrationsTab.tsx | 64 ++++++-- client/src/components/shared/PlaceAvatar.tsx | 14 +- client/src/i18n/translations/ar.ts | 6 + client/src/i18n/translations/br.ts | 6 + client/src/i18n/translations/cs.ts | 6 + client/src/i18n/translations/de.ts | 6 + client/src/i18n/translations/en.ts | 6 + client/src/i18n/translations/es.ts | 6 + client/src/i18n/translations/fr.ts | 6 + client/src/i18n/translations/hu.ts | 6 + client/src/i18n/translations/id.ts | 6 + client/src/i18n/translations/it.ts | 6 + client/src/i18n/translations/nl.ts | 6 + client/src/i18n/translations/pl.ts | 6 + client/src/i18n/translations/ru.ts | 6 + client/src/i18n/translations/zh.ts | 6 + client/src/i18n/translations/zhTw.ts | 6 + client/src/pages/JourneyDetailPage.tsx | 52 ++++--- client/src/pages/SharedTripPage.tsx | 8 +- client/src/pages/TripPlannerPage.tsx | 3 +- client/src/store/journeyStore.test.ts | 65 +++++++- client/src/store/journeyStore.ts | 70 +++++---- client/src/utils/dayMerge.test.ts | 18 ++- client/src/utils/dayMerge.ts | 2 +- client/src/utils/formatters.test.ts | 50 +++++++ client/src/utils/formatters.ts | 12 ++ client/src/utils/uploadQueue.ts | 106 +++++++++++++ client/vite.config.js | 2 +- server/src/app.ts | 2 +- server/src/db/migrations.ts | 36 +++++ server/src/mcp/tools/days.ts | 8 +- server/src/mcp/tools/places.ts | 16 +- server/src/mcp/tools/reservations.ts | 20 ++- server/src/mcp/tools/transports.ts | 22 ++- server/src/routes/oauth.ts | 51 ++++++- server/src/routes/reservations.ts | 6 +- server/src/services/atlasService.ts | 24 ++- server/src/services/budgetService.ts | 11 ++ server/src/services/oauthService.ts | 58 +++++++- server/src/services/tripService.ts | 5 + server/tests/integration/oauth.test.ts | 139 +++++++++++++++++- wiki/MCP-Overview.md | 10 ++ wiki/MCP-Setup.md | 65 ++++---- 54 files changed, 1110 insertions(+), 234 deletions(-) create mode 100644 client/src/utils/formatters.test.ts create mode 100644 client/src/utils/uploadQueue.ts diff --git a/README.md b/README.md index f3dfae0f..b1b68317 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A self-hosted, real-time collaborative travel planner — with maps, budgets, pa
-Demo +Demo   Docker   diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 837ed16b..57c90fbb 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -209,7 +209,7 @@ export const oauthApi = { clients: { list: () => apiClient.get('/oauth/clients').then(r => r.data), - create: (data: { name: string; redirect_uris: string[]; allowed_scopes: string[] }) => + create: (data: { name: string; redirect_uris?: string[]; allowed_scopes: string[]; allows_client_credentials?: boolean }) => apiClient.post('/oauth/clients', data).then(r => r.data), rotate: (id: string) => apiClient.post(`/oauth/clients/${id}/rotate`).then(r => r.data), delete: (id: string) => apiClient.delete(`/oauth/clients/${id}`).then(r => r.data), @@ -407,8 +407,20 @@ export const journeyApi = { reorderEntries: (journeyId: number, orderedIds: number[]) => apiClient.put(`/journeys/${journeyId}/entries/reorder`, { orderedIds }).then(r => r.data), // Photos - uploadPhotos: (entryId: number, formData: FormData) => apiClient.post(`/journeys/entries/${entryId}/photos`, formData, { headers: { 'Content-Type': undefined as any } }).then(r => r.data), - uploadGalleryPhotos: (journeyId: number, formData: FormData) => apiClient.post(`/journeys/${journeyId}/gallery/photos`, formData, { headers: { 'Content-Type': undefined as any } }).then(r => r.data), + uploadPhotos: (entryId: number, formData: FormData, opts?: { onUploadProgress?: (e: import('axios').AxiosProgressEvent) => void; idempotencyKey?: string; signal?: AbortSignal }) => + apiClient.post(`/journeys/entries/${entryId}/photos`, formData, { + headers: { 'Content-Type': undefined as any, ...(opts?.idempotencyKey ? { 'X-Idempotency-Key': opts.idempotencyKey } : {}) }, + timeout: 0, + onUploadProgress: opts?.onUploadProgress, + signal: opts?.signal, + }).then(r => r.data), + uploadGalleryPhotos: (journeyId: number, formData: FormData, opts?: { onUploadProgress?: (e: import('axios').AxiosProgressEvent) => void; idempotencyKey?: string; signal?: AbortSignal }) => + apiClient.post(`/journeys/${journeyId}/gallery/photos`, formData, { + headers: { 'Content-Type': undefined as any, ...(opts?.idempotencyKey ? { 'X-Idempotency-Key': opts.idempotencyKey } : {}) }, + timeout: 0, + onUploadProgress: opts?.onUploadProgress, + signal: opts?.signal, + }).then(r => r.data), addProviderPhotosToGallery: (journeyId: number, provider: string, assetIds: string[], passphrase?: string) => apiClient.post(`/journeys/${journeyId}/gallery/provider-photos`, { provider, asset_ids: assetIds, ...(passphrase ? { passphrase } : {}) }).then(r => r.data), addProviderPhoto: (entryId: number, provider: string, assetId: string, caption?: string, passphrase?: string) => apiClient.post(`/journeys/entries/${entryId}/provider-photos`, { provider, asset_id: assetId, caption, ...(passphrase ? { passphrase } : {}) }).then(r => r.data), addProviderPhotos: (entryId: number, provider: string, assetIds: string[], caption?: string, passphrase?: string) => apiClient.post(`/journeys/entries/${entryId}/provider-photos`, { provider, asset_ids: assetIds, caption, ...(passphrase ? { passphrase } : {}) }).then(r => r.data), diff --git a/client/src/components/Journey/MobileEntryView.tsx b/client/src/components/Journey/MobileEntryView.tsx index 766f8b7f..fc340f4a 100644 --- a/client/src/components/Journey/MobileEntryView.tsx +++ b/client/src/components/Journey/MobileEntryView.tsx @@ -52,7 +52,7 @@ export default function MobileEntryView({ entry, readOnly, publicPhotoUrl, onClo const dateStr = date.toLocaleDateString(undefined, { weekday: 'long', day: 'numeric', month: 'long' }) return ( -
+
{/* Top bar */}
@@ -360,7 +369,15 @@ export default function IntegrationsTab(): React.ReactElement {
-

{client.name}

+
+

{client.name}

+ {client.allows_client_credentials && ( + + {t('settings.oauth.badge.machine')} + + )} +

{t('settings.oauth.clientId')}: {client.client_id} {t('settings.mcp.tokenCreatedAt')} {new Date(client.created_at).toLocaleDateString(locale)} @@ -616,15 +633,26 @@ export default function IntegrationsTab(): React.ReactElement { autoFocus />

-
- -