From 2568b3c0bee9200a0b3c0f53c39da7864d34f09c Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 29 May 2024 10:41:39 +0700 Subject: [PATCH 001/196] fix: #announce room should default to 'Admins Only' for posting --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index df2b0b9d703..91e48065b5b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4743,7 +4743,7 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp false, policyName, undefined, - undefined, + CONST.REPORT.WRITE_CAPABILITIES.ADMINS, // #announce contains all policy members so notifying always should be opt-in only. CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, From e2a6f60a2904d1351d4068230943fc81bbcf4c14 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 16 Jul 2024 16:08:01 -0700 Subject: [PATCH 002/196] Remove front-end workaround for authWrites issue --- src/libs/HttpUtils.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index a826c668be1..f8df7aa6984 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -37,9 +37,6 @@ const abortControllerMap = new Map(); abortControllerMap.set(ABORT_COMMANDS.All, new AbortController()); abortControllerMap.set(ABORT_COMMANDS.SearchForReports, new AbortController()); -// Some existing old commands (6+ years) exempted from the auth writes count check -const exemptedCommandsWithAuthWrites: string[] = ['SetWorkspaceAutoReportingFrequency']; - /** * The API commands that require the skew calculation */ @@ -133,7 +130,7 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form }); } - if (response.jsonCode === CONST.JSON_CODE.MANY_WRITES_ERROR && !exemptedCommandsWithAuthWrites.includes(response.data?.phpCommandName ?? '')) { + if (response.jsonCode === CONST.JSON_CODE.MANY_WRITES_ERROR) { if (response.data) { const {phpCommandName, authWriteCommands} = response.data; // eslint-disable-next-line max-len From 6a2e4d9fdbf60c51b0fc03082615b726b5807f98 Mon Sep 17 00:00:00 2001 From: Rayane Djouah <77965000+rayane-djouah@users.noreply.github.com> Date: Sun, 11 Aug 2024 18:05:19 +0100 Subject: [PATCH 003/196] Make the Room Members view of rooms and expense chats consistent with groups --- src/CONST.ts | 8 + src/ROUTES.ts | 4 + src/SCREENS.ts | 2 + .../ButtonWithDropdownMenu/types.ts | 3 + .../SelectionList/BaseSelectionList.tsx | 2 +- src/components/SelectionList/types.ts | 3 + src/languages/en.ts | 2 + src/languages/es.ts | 2 + .../ModalStackNavigators/index.tsx | 4 + .../Navigators/RightModalNavigator.tsx | 4 + src/libs/Navigation/linkingConfig/config.ts | 5 + src/libs/Navigation/types.ts | 9 + src/pages/RoomMembersDetailsPage.tsx | 137 ++++++++++++++ src/pages/RoomMembersPage.tsx | 177 ++++++++++++------ .../home/report/withReportOrNotFound.tsx | 5 +- 15 files changed, 311 insertions(+), 56 deletions(-) create mode 100644 src/pages/RoomMembersDetailsPage.tsx diff --git a/src/CONST.ts b/src/CONST.ts index eaae7b82ef7..52047b65a3d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -919,6 +919,9 @@ const CONST = { EXPORT_TO_INTEGRATION: 'exportToIntegration', MARK_AS_EXPORTED: 'markAsExported', }, + ROOM_MEMBERS_BULK_ACTION_TYPES: { + REMOVE: 'remove', + }, }, NEXT_STEP: { ICONS: { @@ -4026,6 +4029,11 @@ const CONST = { */ MAX_SELECTION_LIST_PAGE_LENGTH: 500, + /** + * We only include the members search bar when we hit 8 number of members + */ + SHOULD_SHOW_MEMBERS_SEARCH_INPUT_BREAKPOINT: 8, + /** * Bank account names */ diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 27f565929c5..39dc4b6a851 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -354,6 +354,10 @@ const ROUTES = { route: 'r/:reportID/members', getRoute: (reportID: string) => `r/${reportID}/members` as const, }, + ROOM_MEMBERS_DETAILS: { + route: 'r/:reportID/members/:accountID', + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/members/${accountID}` as const, + }, ROOM_INVITE: { route: 'r/:reportID/invite/:role?', getRoute: (reportID: string, role?: string) => `r/${reportID}/invite/${role}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index d125c9658bf..14c2c822f6b 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -152,6 +152,7 @@ const SCREENS = { SIGN_IN: 'SignIn', PRIVATE_NOTES: 'Private_Notes', ROOM_MEMBERS: 'RoomMembers', + ROOM_MEMBERS_DETAILS: 'RoomMembers_Details', ROOM_INVITE: 'RoomInvite', REFERRAL: 'Referral', PROCESS_MONEY_REQUEST_HOLD: 'ProcessMoneyRequestHold', @@ -490,6 +491,7 @@ const SCREENS = { }, ROOM_MEMBERS_ROOT: 'RoomMembers_Root', ROOM_INVITE_ROOT: 'RoomInvite_Root', + ROOM_MEMBERS_DETAILS_ROOT: 'RoomMembersDetails_Root', FLAG_COMMENT_ROOT: 'FlagComment_Root', REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount', GET_ASSISTANCE: 'GetAssistance', diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index e4b81da9494..19d05499c48 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -10,6 +10,8 @@ type PaymentType = DeepValueOf; +type RoomMemberBulkActionType = DeepValueOf; + type WorkspaceDistanceRatesBulkActionType = DeepValueOf; type WorkspaceTaxRatesBulkActionType = DeepValueOf; @@ -98,6 +100,7 @@ type ButtonWithDropdownMenuProps = { export type { PaymentType, WorkspaceMemberBulkActionType, + RoomMemberBulkActionType, WorkspaceDistanceRatesBulkActionType, DropdownOption, ButtonWithDropdownMenuProps, diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index e78d3d43d1e..89d283d7d1d 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -97,6 +97,7 @@ function BaseSelectionList( shouldDelayFocus = true, shouldUpdateFocusedIndex = false, onLongPressRow, + shouldShowTextInput = !!textInputLabel || !!textInputIconLeft, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -105,7 +106,6 @@ function BaseSelectionList( const listRef = useRef>>(null); const innerTextInputRef = useRef(null); const focusTimeoutRef = useRef(null); - const shouldShowTextInput = !!textInputLabel || !!textInputIconLeft; const shouldShowSelectAll = !!onSelectAll; const activeElementRole = useActiveElementRole(); const isFocused = useIsFocused(); diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 782307876e5..c1358f152a2 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -329,6 +329,9 @@ type BaseSelectionListProps = Partial & { /** Callback to fire when an error is dismissed */ onDismissError?: (item: TItem) => void; + /** Whether to show the text input */ + shouldShowTextInput?: boolean; + /** Label for the text input */ textInputLabel?: string; diff --git a/src/languages/en.ts b/src/languages/en.ts index bfe0eef7017..327628867cc 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2999,6 +2999,7 @@ export default { removeMembersTitle: 'Remove members', removeMemberButtonTitle: 'Remove from workspace', removeMemberGroupButtonTitle: 'Remove from group', + removeMemberRoomButtonTitle: 'Remove from room', removeMemberPrompt: ({memberName}: {memberName: string}) => `Are you sure you want to remove ${memberName}?`, removeMemberTitle: 'Remove member', transferOwner: 'Transfer owner', @@ -3581,6 +3582,7 @@ export default { }, }, roomMembersPage: { + roomMembersListTitle: 'Directory of all room members.', memberNotFound: 'Member not found. To invite a new member to the room, please use the invite button above.', notAuthorized: `You don't have access to this page. If you're trying to join this room, just ask a room member to add you. Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: 'Are you sure you want to remove the selected members from the room?', diff --git a/src/languages/es.ts b/src/languages/es.ts index 76d55a09680..2f5f765d936 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3051,6 +3051,7 @@ export default { removeMembersTitle: 'Eliminar miembros', removeMemberButtonTitle: 'Quitar del espacio de trabajo', removeMemberGroupButtonTitle: 'Quitar del grupo', + removeMemberRoomButtonTitle: 'Quitar de la sala', removeMemberPrompt: ({memberName}: {memberName: string}) => `¿Estás seguro de que deseas eliminar a ${memberName}?`, removeMemberTitle: 'Eliminar miembro', transferOwner: 'Transferir la propiedad', @@ -3635,6 +3636,7 @@ export default { }, }, roomMembersPage: { + roomMembersListTitle: 'Directorio de los miembros de la sala.', memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro a la sala de chat, por favor, utiliza el botón invitar que está más arriba.', notAuthorized: `No tienes acceso a esta página. Si estás intentando unirte a esta sala, pide a un miembro de la sala que te añada. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, removeMembersPrompt: '¿Estás seguro de que quieres eliminar a los miembros seleccionados de la sala de chat?', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index f903da87b4e..e0595142fa0 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -156,6 +156,9 @@ const RoomInviteModalStackNavigator = createModalStackNavigator require('../../../../pages/RoomInvitePage').default, }); +const RoomMembersDetailsModalStackNavigator = createModalStackNavigator({ + [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: () => require('../../../../pages/RoomMembersDetailsPage').default, +}); const NewChatModalStackNavigator = createModalStackNavigator({ [SCREENS.NEW_CHAT.ROOT]: () => require('../../../../pages/NewChatSelectorPage').default, [SCREENS.NEW_CHAT.NEW_CHAT_CONFIRM]: () => require('../../../../pages/NewChatConfirmPage').default, @@ -549,6 +552,7 @@ export { ReportSettingsModalStackNavigator, RoomInviteModalStackNavigator, RoomMembersModalStackNavigator, + RoomMembersDetailsModalStackNavigator, SettingsModalStackNavigator, SignInModalStackNavigator, CategoriesModalStackNavigator, diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 44355cbbe95..6835d38739f 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -107,6 +107,10 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { name={SCREENS.RIGHT_MODAL.ROOM_INVITE} component={ModalStackNavigators.RoomInviteModalStackNavigator} /> + ['config'] = { [SCREENS.ROOM_MEMBERS_ROOT]: ROUTES.ROOM_MEMBERS.route, }, }, + [SCREENS.RIGHT_MODAL.ROOM_MEMBERS_DETAILS]: { + screens: { + [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: ROUTES.ROOM_MEMBERS_DETAILS.route, + }, + }, [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: { screens: { [SCREENS.MONEY_REQUEST.START]: ROUTES.MONEY_REQUEST_START.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index a43eab45246..6243132f574 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -771,6 +771,13 @@ type RoomInviteNavigatorParamList = { }; }; +type RoomMembersDetailsNavigatorParamList = { + [SCREENS.ROOM_MEMBERS_DETAILS_ROOT]: { + reportID: string; + accountID: string; + }; +}; + type MoneyRequestNavigatorParamList = { [SCREENS.MONEY_REQUEST.STEP_SEND_FROM]: { iouType: IOUType; @@ -1073,6 +1080,7 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.REPORT_DESCRIPTION]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.PARTICIPANTS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ROOM_MEMBERS]: NavigatorScreenParams; + [SCREENS.RIGHT_MODAL.ROOM_MEMBERS_DETAILS]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.ROOM_INVITE]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.MONEY_REQUEST]: NavigatorScreenParams; [SCREENS.RIGHT_MODAL.NEW_TASK]: NavigatorScreenParams; @@ -1357,6 +1365,7 @@ export type { RightModalNavigatorParamList, RoomInviteNavigatorParamList, RoomMembersNavigatorParamList, + RoomMembersDetailsNavigatorParamList, RootStackParamList, SearchNavigatorParamList, SettingsNavigatorParamList, diff --git a/src/pages/RoomMembersDetailsPage.tsx b/src/pages/RoomMembersDetailsPage.tsx new file mode 100644 index 00000000000..b2f8da10c7b --- /dev/null +++ b/src/pages/RoomMembersDetailsPage.tsx @@ -0,0 +1,137 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import Avatar from '@components/Avatar'; +import Button from '@components/Button'; +import ConfirmModal from '@components/ConfirmModal'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Report from '@libs/actions/Report'; +import Navigation from '@navigation/Navigation'; +import type {RoomMembersDetailsNavigatorParamList} from '@navigation/types'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; +import NotFoundPage from './ErrorPage/NotFoundPage'; +import withReportOrNotFound from './home/report/withReportOrNotFound'; +import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; + +type RoomMembersDetailsPageOnyxProps = { + /** Personal details of all users */ + personalDetails: OnyxEntry; +}; + +type RoomMembersDetailsPagePageProps = WithReportOrNotFoundProps & + StackScreenProps & + RoomMembersDetailsPageOnyxProps; + +function RoomMembersDetailsPage({personalDetails, report, route}: RoomMembersDetailsPagePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const StyleUtils = useStyleUtils(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + + const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = React.useState(false); + + const accountID = Number(route.params.accountID); + const backTo = ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1'); + + const member = report?.participants?.[accountID]; + const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); + const fallbackIcon = details.fallbackIcon ?? ''; + const displayName = details.displayName ?? ''; + const isSelectedMemberCurrentUser = accountID === currentUserPersonalDetails?.accountID; + const removeUser = useCallback(() => { + setIsRemoveMemberConfirmModalVisible(false); + Report.removeFromGroupChat(report?.reportID, [accountID]); + Navigation.goBack(backTo); + }, [backTo, report, accountID]); + + const navigateToProfile = useCallback(() => { + Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute())); + }, [accountID]); + + if (!member) { + return ; + } + + return ( + + Navigation.goBack(backTo)} + /> + + + + {!!(details.displayName ?? '') && ( + + {displayName} + + )} + <> +