From 69db17b5ddeef8b07f78a6215e994d70ad7b9515 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 9 Oct 2026 15:44:13 +0200 Subject: [PATCH] refactor(vacay): draw the phone user picker list with one component The invite and the share sheet each carried the same unfolded user list. Both now render MUserPickerList with the users, the picked id and the pick handler from useVacayUserPicker. The markup is unchanged. --- .../components/MUserPickerList.test.tsx | 39 +++++++++++++++++++ .../src/mobile/components/MUserPickerList.tsx | 28 +++++++++++++ .../screens/vacay/MVacayInviteSheet.tsx | 18 +-------- .../mobile/screens/vacay/MVacayShareSheet.tsx | 18 +-------- 4 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 client/src/mobile/components/MUserPickerList.test.tsx create mode 100644 client/src/mobile/components/MUserPickerList.tsx diff --git a/client/src/mobile/components/MUserPickerList.test.tsx b/client/src/mobile/components/MUserPickerList.test.tsx new file mode 100644 index 000000000..cd2a9908f --- /dev/null +++ b/client/src/mobile/components/MUserPickerList.test.tsx @@ -0,0 +1,39 @@ +// FE-COMP-MUSERPICK-001 to FE-COMP-MUSERPICK-003 +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import MUserPickerList from './MUserPickerList'; + +const users = [ + { id: 2, username: 'bob' }, + { id: 3, username: 'carol' }, +]; + +describe('MUserPickerList', () => { + it('FE-COMP-MUSERPICK-001: renders one button per user and tints none while nothing is picked', () => { + render(); + + const rows = screen.getAllByRole('button'); + expect(rows.map((b) => b.textContent)).toEqual(['bob', 'carol']); + for (const row of rows) { + expect(row).toHaveAttribute('type', 'button'); + expect(row.className).not.toContain('bg-[color:var(--m-ic)]'); + } + }); + + it('FE-COMP-MUSERPICK-002: tints only the picked user', () => { + render(); + + expect(screen.getByRole('button', { name: 'carol' }).className).toContain('bg-[color:var(--m-ic)]'); + expect(screen.getByRole('button', { name: 'bob' }).className).not.toContain('bg-[color:var(--m-ic)]'); + }); + + it('FE-COMP-MUSERPICK-003: hands the tapped user id to onPick', () => { + const onPick = vi.fn(); + render(); + + fireEvent.click(screen.getByRole('button', { name: 'bob' })); + + expect(onPick).toHaveBeenCalledWith(2); + }); +}); diff --git a/client/src/mobile/components/MUserPickerList.tsx b/client/src/mobile/components/MUserPickerList.tsx new file mode 100644 index 000000000..d0ef6945a --- /dev/null +++ b/client/src/mobile/components/MUserPickerList.tsx @@ -0,0 +1,28 @@ +interface MUserPickerListProps { + users: { id: number; username: string }[]; + selected: number | null; + onPick: (id: number) => void; +} + +/** + * The unfolded user list under a phone sheet's user picker (the vacay invite + * and share sheets): one row per user, the picked one tinted. + */ +export default function MUserPickerList({ users, selected, onPick }: MUserPickerListProps) { + return ( +
+ {users.map((u) => ( + + ))} +
+ ); +} diff --git a/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx b/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx index 251f9f2b7..1f258d664 100644 --- a/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx +++ b/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx @@ -1,6 +1,7 @@ import { ChevronDown, Clock, Loader2, X } from 'lucide-react' import MSheet from '../../components/MSheet' import MIconBtn from '../../components/MIconBtn' +import MUserPickerList from '../../components/MUserPickerList' import { useVacayStore } from '../../../store/vacayStore' import { useTranslation } from '../../../i18n' import { type VacayUser } from '../../../types' @@ -59,22 +60,7 @@ export default function MVacayInviteSheet({ open, onClose }: MVacayInviteSheetPr - {pickerOpen && ( -
- {available.map(u => ( - - ))} -
- )} + {pickerOpen && } )} diff --git a/client/src/mobile/screens/vacay/MVacayShareSheet.tsx b/client/src/mobile/screens/vacay/MVacayShareSheet.tsx index 712b917b5..b1ab621f0 100644 --- a/client/src/mobile/screens/vacay/MVacayShareSheet.tsx +++ b/client/src/mobile/screens/vacay/MVacayShareSheet.tsx @@ -1,6 +1,7 @@ import { ChevronDown, Eye, EyeOff, Loader2, Share2, X } from 'lucide-react' import MSheet from '../../components/MSheet' import MIconBtn from '../../components/MIconBtn' +import MUserPickerList from '../../components/MUserPickerList' import { useVacayStore } from '../../../store/vacayStore' import { useTranslation } from '../../../i18n' import { useVacayShareActions } from '../../../components/Vacay/useVacayShareActions' @@ -71,22 +72,7 @@ export default function MVacayShareSheet({ open, onClose }: MVacayShareSheetProp {t('vacay.share')} - {pickerOpen && ( -
- {available.map(u => ( - - ))} -
- )} + {pickerOpen && } )}