feat(dashboard): mobile layout, glass UI, context bottom nav + OIDC PKCE (#1079)

* feat(dashboard): mobile layout, glass tiles, plain-text countdown, place photos

- Rework the mobile dashboard: cover hero, separate boarding-pass card,
  trimmed atlas (trips + days only), stacked widgets
- New floating bottom tab bar with a centred context-aware + button
  (new trip / place / journey / entry depending on the page)
- Move profile + notifications into a small top strip on the dashboard
- Desktop: glassmorphic tiles (light + dark), neutral dark palette,
  plain-text countdown module, real place photos in the boarding pass

* i18n(dashboard): translate new dashboard keys across all locales

Fill the dashboard-rework keys (hero, atlas, fx, tz, upcoming, copy
dialog, aria labels, countdown) that were left as English placeholders,
plus the new startsIn/aria keys, for all 19 languages.

* feat(oidc): send PKCE (S256) in the OIDC login flow

The OIDC client now generates a code_verifier per login, sends the
S256 code_challenge on the authorize request and the code_verifier on
the token exchange. Works whether the provider has PKCE optional or
required (fixes login against providers that require PKCE, e.g. Pocket ID).
This commit is contained in:
Maurice
2026-05-27 23:19:03 +02:00
committed by GitHub
parent 0d2657ee37
commit 6d2dd37414
34 changed files with 1692 additions and 1296 deletions
+10 -1
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'
import ReactDOM from 'react-dom'
import { useParams, useNavigate } from 'react-router-dom'
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
import { useTripStore } from '../store/tripStore'
import { useCanDo } from '../store/permissionsStore'
import { useSettingsStore } from '../store/settingsStore'
@@ -261,6 +261,15 @@ export default function TripPlannerPage(): React.ReactElement | null {
const [editingPlace, setEditingPlace] = useState<Place | null>(null)
const [prefillCoords, setPrefillCoords] = useState<{ lat: number; lng: number; name?: string; address?: string } | null>(null)
const [editingAssignmentId, setEditingAssignmentId] = useState<number | null>(null)
const [searchParams, setSearchParams] = useSearchParams()
// The bottom-nav "+" opens the new-place form via ?create=place.
useEffect(() => {
if (searchParams.get('create') === 'place') {
setEditingPlace(null); setEditingAssignmentId(null); setShowPlaceForm(true)
setSearchParams(p => { p.delete('create'); return p }, { replace: true })
}
}, [searchParams])
const [showTripForm, setShowTripForm] = useState<boolean>(false)
const [showMembersModal, setShowMembersModal] = useState<boolean>(false)
const [showReservationModal, setShowReservationModal] = useState<boolean>(false)