diff --git a/native/roles/create-roles-header-right-button.react.js b/native/roles/create-roles-header-right-button.react.js index e7e3f5c0b..ba54f39f4 100644 --- a/native/roles/create-roles-header-right-button.react.js +++ b/native/roles/create-roles-header-right-button.react.js @@ -1,88 +1,103 @@ // @flow import { useNavigation } from '@react-navigation/native'; import * as React from 'react'; import { TouchableOpacity, Text } from 'react-native'; import { modifyCommunityRole, modifyCommunityRoleActionTypes, } from 'lib/actions/thread-actions.js'; import { useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils.js'; +import { values } from 'lib/utils/objects.js'; import type { NavigationRoute } from '../navigation/route-names'; import { useStyles } from '../themes/colors.js'; type Props = { +route: NavigationRoute<'CreateRolesScreen'>, + +setRoleCreationFailed: boolean => mixed, }; function CreateRolesHeaderRightButton(props: Props): React.Node { const { threadInfo, action, roleName, rolePermissions } = props.route.params; + const { setRoleCreationFailed } = props; const navigation = useNavigation(); const styles = useStyles(unboundStyles); const callModifyCommunityRole = useServerCall(modifyCommunityRole); const dispatchActionPromise = useDispatchActionPromise(); + const threadRoleNames = React.useMemo( + () => values(threadInfo.roles).map(role => role.name), + [threadInfo.roles], + ); + const onPressCreate = React.useCallback(() => { + if (threadRoleNames.includes(roleName)) { + setRoleCreationFailed(true); + return; + } + dispatchActionPromise( modifyCommunityRoleActionTypes, callModifyCommunityRole({ community: threadInfo.id, action, name: roleName, permissions: [...rolePermissions], }), ); navigation.goBack(); }, [ callModifyCommunityRole, dispatchActionPromise, threadInfo, action, roleName, rolePermissions, navigation, + setRoleCreationFailed, + threadRoleNames, ]); const shouldHeaderRightBeDisabled = roleName.length === 0; const createButton = React.useMemo(() => { const textStyle = shouldHeaderRightBeDisabled ? styles.createButtonDisabled : styles.createButton; return ( Create ); }, [ shouldHeaderRightBeDisabled, styles.createButtonDisabled, styles.createButton, onPressCreate, ]); return createButton; } const unboundStyles = { createButtonDisabled: { color: 'disabledButton', marginRight: 10, }, createButton: { color: 'purpleLink', marginRight: 10, }, }; export default CreateRolesHeaderRightButton; diff --git a/native/roles/create-roles-screen.react.js b/native/roles/create-roles-screen.react.js index b37eae567..d4ed1f438 100644 --- a/native/roles/create-roles-screen.react.js +++ b/native/roles/create-roles-screen.react.js @@ -1,254 +1,288 @@ // @flow import * as React from 'react'; import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import { modifyCommunityRoleActionTypes } from 'lib/actions/thread-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import { type UserSurfacedPermissionOption, type UserSurfacedPermission, } from 'lib/types/thread-permission-types.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import { useFilterPermissionOptionsByThreadType } from 'lib/utils/role-utils.js'; import CreateRolesHeaderRightButton from './create-roles-header-right-button.react.js'; import type { RolesNavigationProp } from './roles-navigator.react.js'; import EnumSettingsOption from '../components/enum-settings-option.react.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import TextInput from '../components/text-input.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; export type CreateRolesScreenParams = { +threadInfo: ThreadInfo, +action: 'create_role' | 'edit_role', +roleName: string, +rolePermissions: $ReadOnlySet, }; type CreateRolesScreenProps = { +navigation: RolesNavigationProp<'CreateRolesScreen'>, +route: NavigationRoute<'CreateRolesScreen'>, }; const createRolesLoadingStatusSelector = createLoadingStatusSelector( modifyCommunityRoleActionTypes, ); function CreateRolesScreen(props: CreateRolesScreenProps): React.Node { const { threadInfo, action, roleName: defaultRoleName, rolePermissions: defaultRolePermissions, } = props.route.params; const createRolesLoadingStatus: LoadingStatus = useSelector( createRolesLoadingStatusSelector, ); const [customRoleName, setCustomRoleName] = React.useState(defaultRoleName); const [selectedPermissions, setSelectedPermissions] = React.useState< $ReadOnlySet, >(defaultRolePermissions); + const [roleCreationFailed, setRoleCreationFailed] = + React.useState(false); + const styles = useStyles(unboundStyles); + const errorStyles = React.useMemo( + () => + roleCreationFailed + ? [styles.errorContainer, styles.errorContainerVisible] + : styles.errorContainer, + [roleCreationFailed, styles.errorContainer, styles.errorContainerVisible], + ); + const onClearPermissions = React.useCallback(() => { setSelectedPermissions(new Set()); }, []); const isSelectedPermissionsEmpty = selectedPermissions.size === 0; const clearPermissionsText = React.useMemo(() => { const textStyle = isSelectedPermissionsEmpty ? styles.clearPermissionsTextDisabled : styles.clearPermissionsText; return ( Clear permissions ); }, [ isSelectedPermissionsEmpty, onClearPermissions, styles.clearPermissionsText, styles.clearPermissionsTextDisabled, ]); const isUserSurfacedPermissionSelected = React.useCallback( (option: UserSurfacedPermissionOption) => selectedPermissions.has(option.userSurfacedPermission), [selectedPermissions], ); const onEnumValuePress = React.useCallback( (option: UserSurfacedPermissionOption) => setSelectedPermissions(currentPermissions => { if (currentPermissions.has(option.userSurfacedPermission)) { const newPermissions = new Set(currentPermissions); newPermissions.delete(option.userSurfacedPermission); return newPermissions; } else { return new Set([ ...currentPermissions, option.userSurfacedPermission, ]); } }), [], ); React.useEffect( () => props.navigation.setParams({ threadInfo, action, roleName: customRoleName, rolePermissions: selectedPermissions, }), [props.navigation, threadInfo, action, customRoleName, selectedPermissions], ); const filteredUserSurfacedPermissionOptions = useFilterPermissionOptionsByThreadType(threadInfo.type); const permissionsList = React.useMemo( () => [...filteredUserSurfacedPermissionOptions].map(permission => ( onEnumValuePress(permission)} /> )), [ isUserSurfacedPermissionSelected, filteredUserSurfacedPermissionOptions, onEnumValuePress, ], ); const onChangeRoleNameInput = React.useCallback((roleName: string) => { + setRoleCreationFailed(false); setCustomRoleName(roleName); }, []); React.useEffect( () => props.navigation.setOptions({ // eslint-disable-next-line react/display-name headerRight: () => { if (createRolesLoadingStatus === 'loading') { return ( ); } - return ; + return ( + + ); }, }), [ createRolesLoadingStatus, props.navigation, styles.activityIndicator, props.route, ], ); return ( ROLE NAME + + + There is already a role with this name in the community + + PERMISSIONS {clearPermissionsText} {permissionsList} ); } const unboundStyles = { roleNameContainer: { marginTop: 30, }, roleNameText: { color: 'panelBackgroundLabel', fontSize: 12, marginBottom: 5, marginLeft: 10, }, roleInput: { backgroundColor: 'panelForeground', padding: 12, flexDirection: 'row', justifyContent: 'space-between', }, roleInputComponent: { color: 'panelForegroundLabel', fontSize: 16, }, pencilIcon: { color: 'panelInputSecondaryForeground', }, + errorContainer: { + marginTop: 10, + alignItems: 'center', + opacity: 0, + }, + errorContainerVisible: { + opacity: 1, + }, + errorText: { + color: 'redText', + fontSize: 14, + }, permissionsContainer: { marginTop: 20, paddingBottom: 220, }, permissionsHeader: { flexDirection: 'row', justifyContent: 'space-between', }, permissionsText: { color: 'panelBackgroundLabel', fontSize: 12, marginLeft: 10, }, clearPermissionsText: { color: 'purpleLink', fontSize: 12, marginRight: 15, }, clearPermissionsTextDisabled: { color: 'disabledButton', fontSize: 12, marginRight: 15, }, permissionsListContainer: { backgroundColor: 'panelForeground', marginTop: 10, }, activityIndicator: { paddingRight: 15, }, }; export default CreateRolesScreen;