diff --git a/client/src/mobile/components/MWalletButton.test.tsx b/client/src/mobile/components/MWalletButton.test.tsx index 32a7fa254..075a4d56e 100644 --- a/client/src/mobile/components/MWalletButton.test.tsx +++ b/client/src/mobile/components/MWalletButton.test.tsx @@ -61,12 +61,14 @@ describe('MWalletButton', () => { await waitFor(() => expect(onError).toHaveBeenLastCalledWith(expect.stringMatching(/could not be made/i))); }); - it('on Android adds the booking to Google Wallet, under its own name', async () => { + it('on Android adds the booking to Google Wallet, under its own name for screen readers', async () => { wallet.kind = 'google'; wallet.addGoogle.mockResolvedValue('added'); const onError = vi.fn(); render(); - expect(screen.getByRole('button')).toHaveTextContent(/Google Wallet/); + // The label is short, the accessible name says which wallet. + expect(screen.getByRole('button')).toHaveTextContent(/^Wallet$/); + expect(screen.getByRole('button', { name: /Google Wallet/ })).toBeInTheDocument(); fireEvent.click(screen.getByRole('button')); await waitFor(() => expect(wallet.addGoogle).toHaveBeenCalledWith(42, 7, expect.any(String))); expect(wallet.add).not.toHaveBeenCalled(); diff --git a/client/src/mobile/components/MWalletButton.tsx b/client/src/mobile/components/MWalletButton.tsx index 49c052786..2b79635ab 100644 --- a/client/src/mobile/components/MWalletButton.tsx +++ b/client/src/mobile/components/MWalletButton.tsx @@ -57,10 +57,11 @@ export default function MWalletButton({ tripId, reservationId, onError, classNam onClick={() => void add()} disabled={busy} aria-busy={busy} + aria-label={t(wallet === 'google' ? 'reservations.wallet.addGoogle' : 'reservations.wallet.add')} className="flex items-center gap-[6px] rounded-full bg-black px-3 py-[7px] text-[length:calc(12px*var(--fs-scale-body,1))] font-semibold text-white transition-transform active:scale-[.97] disabled:opacity-70" // theme-lint-disable: the wallets' badges are black in either theme > {busy ? : } - {t(wallet === 'google' ? 'reservations.wallet.addGoogle' : 'reservations.wallet.add')} + {t('reservations.wallet.short')} ); return className ?
{button}
: button; diff --git a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx index 3da13e163..d40babee6 100644 --- a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx +++ b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx @@ -180,14 +180,8 @@ function RoadtripStagePage({ planner, shell, dayId: pageDayId, alts, chrome }: O // A chip that may not be opened is not a button in the first place (`bookingOpens`), // so nothing is turned away silently here. const canEditBookings = planner.can('reservation_edit', planner.trip) - const openBooking = (res: Reservation) => { - if (planner.TRANSPORT_TYPES.has(res.type)) { - shell.openSheet('transport', { reservationId: res.id }) - return - } - planner.setEditingReservation(res) - planner.setShowReservationModal(true) - } + // Every booking opens its detail sheet, as on the desktop; its pencil edits it there. + const openBooking = (res: Reservation) => shell.openSheet('transport', { reservationId: res.id }) // A booked night at the edge of the stage is no stop, and has no stop sheet: it opens the // booking behind the night, else the stay for somebody who may edit days, else the // hotel's place, the way a stay chip in the day timeline does (#2210). diff --git a/client/src/mobile/screens/trip/sheets/MDaySheet.tsx b/client/src/mobile/screens/trip/sheets/MDaySheet.tsx index 0f2d11e00..9bac8813d 100644 --- a/client/src/mobile/screens/trip/sheets/MDaySheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MDaySheet.tsx @@ -111,19 +111,11 @@ export default function MDaySheet({ planner, shell }: MTripSheetsProps) { } } + // A booking opens its detail sheet, as on the desktop; its pencil edits it there. const openReservation = (r: Reservation) => { const isTransport = planner.TRANSPORT_TYPES.has(r.type) - if (isTransport && canEditDays) { - planner.setEditingTransport(r) - planner.setTransportModalDayId(r.day_id ?? null) - planner.setShowTransportModal(true) - } else if (!isTransport && canEditReservations) { - planner.setEditingReservation(r) - planner.setShowReservationModal(true) - } else { - return - } - shell.closeSheet() + if (isTransport ? !canEditDays : !canEditReservations) return + shell.openSheet('transport', { reservationId: r.id }) } const planTransit = () => { diff --git a/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx b/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx index 0344bebf4..03b057fbe 100644 --- a/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MPlaceSheet.tsx @@ -112,18 +112,11 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) { ) const openRes = (res: Reservation) => { if (!canOpenRes(res)) return - if (planner.TRANSPORT_TYPES.has(res.type)) { - planner.setEditingTransport(res) - planner.setTransportModalDayId(res.day_id ?? null) - planner.setTransportModalAutomated(false) - planner.setShowTransportModal(true) - } else { - planner.setEditingReservation(res) - planner.setShowReservationModal(true) - } // This sheet hangs off the place selection, not the sheet stack, so its own - // close() is what dismisses it — otherwise the editor opens on top of it. + // close() is what dismisses it; the booking's detail sheet then opens alone, + // with its pencil for editing, as on the desktop. close() + shell.openSheet('transport', { reservationId: res.id }) } const participants = assignmentInDay?.participants || [] @@ -413,7 +406,6 @@ export default function MPlaceSheet({ planner, shell }: MTripSheetsProps) { type="button" onClick={() => openRes(res)} disabled={!canOpenRes(res)} - aria-label={canOpenRes(res) ? t('inspector.editRes') : undefined} className={`flex w-full items-center gap-2 rounded-[14px] px-3 py-[10px] text-start ${INNER_CLS}`} > s.settings.time_format) || '24h' const blurCodes = useSettingsStore(s => s.settings.blur_booking_codes) const [codeRevealed, setCodeRevealed] = useState(false) @@ -118,6 +119,9 @@ export default function MTransportSheet({ planner, shell }: MTripSheetsProps) { } const meta = parseMetadata(res) + // A transport is part of the day plan, a stay, a table or a ticket a booking: + // each is edited under its own permission, as in the tab it belongs to. + const canEdit = isTransportReservationType(res.type) ? canEditDays : canEditBookings const ResIcon = RES_ICONS[res.type as keyof typeof RES_ICONS] || RES_ICONS.other const from = (res.endpoints || []).find(e => e.role === 'from') @@ -362,7 +366,7 @@ export default function MTransportSheet({ planner, shell }: MTripSheetsProps) { {f.original_name} ))} - {canEditDays && ( + {canEdit && (