-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[Domain Control] [Release 4] [FE] Move single user between domain groups #88462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
83ce131
7620815
09dbb22
4910f7f
915ec47
e304bd7
21925c4
be5cc45
6f9e127
6fa3a55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import {domainNameSelector, groupsSelector, selectSecurityGroupForAccount} from '@selectors/Domain'; | ||
| import React, {useState} from 'react'; | ||
| import type {OnyxEntry} from 'react-native-onyx'; | ||
| import Button from '@components/Button'; | ||
| import FixedFooter from '@components/FixedFooter'; | ||
| import HeaderWithBackButton from '@components/HeaderWithBackButton'; | ||
| import ScreenWrapper from '@components/ScreenWrapper'; | ||
| import SelectionList from '@components/SelectionList'; | ||
| import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem'; | ||
| import type {ListItem} from '@components/SelectionList/ListItem/types'; | ||
| import Text from '@components/Text'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {changeDomainSecurityGroup} from '@libs/actions/Domain'; | ||
| import Navigation from '@navigation/Navigation'; | ||
| import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; | ||
| import type {SettingsNavigatorParamList} from '@navigation/types'; | ||
| import DomainNotFoundPageWrapper from '@pages/domain/DomainNotFoundPageWrapper'; | ||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import ROUTES from '@src/ROUTES'; | ||
| import type SCREENS from '@src/SCREENS'; | ||
| import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails'; | ||
| import type {Domain} from '@src/types/onyx'; | ||
|
|
||
| type SecurityGroupItem = ListItem & { | ||
| value: string; | ||
| }; | ||
|
|
||
| type MoveUserBetweenGroupsPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.DOMAIN.MEMBER_MOVE_TO_GROUP>; | ||
|
|
||
| function MoveUserBetweenGroupsPage({route}: MoveUserBetweenGroupsPageProps) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This component is pretty much overlapped with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there's meaningful overlap. The shared parts are: the SecurityGroupItem type, the securityGroups to list items mapping, handleSelectRow, and the entire JSX skeleton (wrapper, header, subtitle, selection list, footer button). The differences are in data sourcing and save behavior: Single-user page: gets accountID from route params, pre-selects current group, single changeDomainSecurityGroup call, navigates back to member details, checks selectedGroupId === currentGroupId and memberLogin for disabled state. Bulk page: gets selectedMemberAccountIDs from Onyx, no pre-selection, loops changeDomainSecurityGroup for each member, navigates back to members list, only checks selectedGroupId for disabled state. One approach: extract a shared SelectSecurityGroupPage component that accepts props for the subtitle, onSave callback, isDisabled, onBack, and initiallySelectedGroupId, and renders the common shell. Each page becomes a thin wrapper that supplies its specific data and save logic. This keeps the UI DRY without forcing a premature abstraction over the behavioral differences.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we try this? Btw not blocker.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jakubstec Yeah maybe something for a follow up but I agree this is not a blocker |
||
| const {domainAccountID, accountID} = route.params; | ||
| const styles = useThemeStyles(); | ||
| const {translate} = useLocalize(); | ||
|
|
||
| const [selectedGroupId, setSelectedGroupId] = useState<string | undefined>(); | ||
| const [domainName] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: domainNameSelector}); | ||
| const [securityGroups] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: groupsSelector}); | ||
|
|
||
| const securityGroupSelector = (domain: OnyxEntry<Domain>) => selectSecurityGroupForAccount(accountID)(domain); | ||
| const [userSecurityGroup] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, { | ||
| selector: securityGroupSelector, | ||
| }); | ||
| const [memberLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(accountID)}); | ||
|
|
||
| const currentGroupId = userSecurityGroup?.key.replace(CONST.DOMAIN.DOMAIN_SECURITY_GROUP_PREFIX, ''); | ||
|
|
||
| const data: SecurityGroupItem[] = (securityGroups ?? []).map(({id, details}) => ({ | ||
| text: details.name ?? '', | ||
| keyForList: id, | ||
| value: id, | ||
| isSelected: id === (selectedGroupId ?? currentGroupId), | ||
| })); | ||
|
|
||
| const handleSelectRow = (item: SecurityGroupItem) => { | ||
| setSelectedGroupId(item.value); | ||
| }; | ||
|
|
||
| const handleSave = () => { | ||
| if (!selectedGroupId || !domainName || !userSecurityGroup || !memberLogin) { | ||
| return; | ||
| } | ||
|
|
||
| if (selectedGroupId === currentGroupId) { | ||
| Navigation.goBack(ROUTES.DOMAIN_MEMBER_DETAILS.getRoute(domainAccountID, accountID)); | ||
| return; | ||
| } | ||
|
|
||
| const newSecurityGroupKey: `${typeof CONST.DOMAIN.DOMAIN_SECURITY_GROUP_PREFIX}${string}` = `${CONST.DOMAIN.DOMAIN_SECURITY_GROUP_PREFIX}${selectedGroupId}`; | ||
| changeDomainSecurityGroup(domainAccountID, domainName, memberLogin, accountID, userSecurityGroup.key, userSecurityGroup.securityGroup, newSecurityGroupKey); | ||
| Navigation.goBack(ROUTES.DOMAIN_MEMBER_DETAILS.getRoute(domainAccountID, accountID)); | ||
| }; | ||
|
|
||
| return ( | ||
| <DomainNotFoundPageWrapper domainAccountID={domainAccountID}> | ||
| <ScreenWrapper | ||
| shouldEnableMaxHeight | ||
| testID="MoveUserBetweenGroupsPage" | ||
| includeSafeAreaPaddingBottom | ||
| > | ||
| <HeaderWithBackButton | ||
| title={translate('domain.members.moveToGroup')} | ||
| onBackButtonPress={() => { | ||
| Navigation.goBack(ROUTES.DOMAIN_MEMBER_DETAILS.getRoute(domainAccountID, accountID)); | ||
| }} | ||
| /> | ||
| <Text style={[styles.ph5, styles.pb3, styles.textSupporting]}>{translate('domain.members.chooseWhereToMoveName', {name: memberLogin ?? ''})}</Text> | ||
|
|
||
| <SelectionList<SecurityGroupItem> | ||
| data={data} | ||
| onSelectRow={handleSelectRow} | ||
| ListItem={SingleSelectListItem} | ||
| initiallyFocusedItemKey={currentGroupId} | ||
| /> | ||
| <FixedFooter> | ||
| <Button | ||
| success | ||
| large | ||
| pressOnEnter | ||
| text={translate('common.save')} | ||
| onPress={handleSave} | ||
| isDisabled={!selectedGroupId || selectedGroupId === currentGroupId || !memberLogin} | ||
| /> | ||
| </FixedFooter> | ||
| </ScreenWrapper> | ||
| </DomainNotFoundPageWrapper> | ||
| ); | ||
| } | ||
|
|
||
| export default MoveUserBetweenGroupsPage; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs