diff --git a/web/settings/relationship/add-users-list-modal.react.js b/web/settings/relationship/add-users-list-modal.react.js index 41cc7f921..19735f86d 100644 --- a/web/settings/relationship/add-users-list-modal.react.js +++ b/web/settings/relationship/add-users-list-modal.react.js @@ -1,65 +1,135 @@ // @flow import * as React from 'react'; +import { + updateRelationships, + updateRelationshipsActionTypes, +} from 'lib/actions/relationship-actions.js'; +import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { UserRelationshipStatus, RelationshipAction, } from 'lib/types/relationship-types.js'; +import { useLegacyAshoatKeyserverCall } from 'lib/utils/action-utils.js'; +import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; +import css from './add-users-list.css'; import AddUsersList from './add-users-list.react.js'; import type { ButtonColor } from '../../components/button.react.js'; +import Button from '../../components/button.react.js'; +import LoadingIndicator from '../../loading-indicator.react.js'; import SearchModal from '../../modals/search-modal.react.js'; +import { useSelector } from '../../redux/redux-utils.js'; + +const loadingStatusSelector = createLoadingStatusSelector( + updateRelationshipsActionTypes, +); type Props = { +closeModal: () => void, +name: string, +excludedStatuses: $ReadOnlySet, +confirmButtonContent: React.Node, +confirmButtonColor?: ButtonColor, +relationshipAction: RelationshipAction, }; function AddUsersListModal(props: Props): React.Node { const { closeModal, name, excludedStatuses, confirmButtonContent, confirmButtonColor, relationshipAction, } = props; + const [errorMessage, setErrorMessage] = React.useState(''); + const addUsersListChildGenerator = React.useCallback( (searchText: string) => ( ), - [ - excludedStatuses, - confirmButtonContent, - confirmButtonColor, - relationshipAction, - closeModal, - ], + [excludedStatuses, errorMessage], + ); + + const callUpdateRelationships = + useLegacyAshoatKeyserverCall(updateRelationships); + + const dispatchActionPromise = useDispatchActionPromise(); + + const updateRelationshipsPromiseCreator = React.useCallback(async () => { + try { + setErrorMessage(''); + const result = await callUpdateRelationships({ + action: relationshipAction, + userIDs: [], // TODO: re-add pending users + }); + closeModal(); + return result; + } catch (e) { + setErrorMessage('unknown error'); + throw e; + } + }, [callUpdateRelationships, closeModal, relationshipAction]); + + const confirmSelection = React.useCallback( + () => + dispatchActionPromise( + updateRelationshipsActionTypes, + updateRelationshipsPromiseCreator(), + ), + [dispatchActionPromise, updateRelationshipsPromiseCreator], ); + const loadingStatus = useSelector(loadingStatusSelector); + + const primaryButton = React.useMemo(() => { + let buttonContent = confirmButtonContent; + + if (loadingStatus === 'loading') { + buttonContent = ( + <> +
{confirmButtonContent}
+ + + ); + } + + return ( + + ); + }, [ + confirmButtonColor, + confirmButtonContent, + confirmSelection, + loadingStatus, + ]); + return ( {addUsersListChildGenerator} ); } export default AddUsersListModal; diff --git a/web/settings/relationship/add-users-list.css b/web/settings/relationship/add-users-list.css index d161fc6fe..e7de0e5b8 100644 --- a/web/settings/relationship/add-users-list.css +++ b/web/settings/relationship/add-users-list.css @@ -1,59 +1,54 @@ .container { height: 625px; display: flex; flex-direction: column; } .userTagsContainer { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px; } .userRowsContainer { overflow: auto; display: flex; flex-direction: column; flex: 1; margin-bottom: 8px; } .addUserButton { justify-content: space-between; padding: 16px; color: var(--relationship-modal-color); font-size: var(--l-font-18); line-height: var(--line-height-display); } .addUserButtonUsername { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.buttons { - display: flex; - justify-content: space-between; -} - .confirmButtonContainer { display: flex; flex-direction: column; align-items: center; } .hidden { visibility: hidden; height: 0; } .error { padding-bottom: 8px; font-size: var(--s-font-14); line-height: var(--line-height-display); color: var(--error); padding-left: 6px; font-style: italic; } diff --git a/web/settings/relationship/add-users-list.react.js b/web/settings/relationship/add-users-list.react.js index bf6468916..015e933fb 100644 --- a/web/settings/relationship/add-users-list.react.js +++ b/web/settings/relationship/add-users-list.react.js @@ -1,253 +1,176 @@ // @flow import * as React from 'react'; -import { - updateRelationships, - updateRelationshipsActionTypes, -} from 'lib/actions/relationship-actions.js'; import { useENSNames } from 'lib/hooks/ens-cache.js'; -import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { useUserSearchIndex } from 'lib/selectors/nav-selectors.js'; import { useSearchUsers } from 'lib/shared/search-utils.js'; -import type { - UserRelationshipStatus, - RelationshipAction, -} from 'lib/types/relationship-types.js'; +import type { UserRelationshipStatus } from 'lib/types/relationship-types.js'; import type { GlobalAccountUserInfo, AccountUserInfo, } from 'lib/types/user-types.js'; -import { useLegacyAshoatKeyserverCall } from 'lib/utils/action-utils.js'; import { values } from 'lib/utils/objects.js'; -import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import AddUsersListItem from './add-users-list-item.react.js'; import css from './add-users-list.css'; -import Button from '../../components/button.react.js'; -import type { ButtonColor } from '../../components/button.react.js'; import Label from '../../components/label.react.js'; -import LoadingIndicator from '../../loading-indicator.react.js'; import { useSelector } from '../../redux/redux-utils.js'; -const loadingStatusSelector = createLoadingStatusSelector( - updateRelationshipsActionTypes, -); - type Props = { +searchText: string, +excludedStatuses?: $ReadOnlySet, - +closeModal: () => void, - +confirmButtonContent: React.Node, - +confirmButtonColor?: ButtonColor, - +relationshipAction: RelationshipAction, + +errorMessage: string, }; function AddUsersList(props: Props): React.Node { - const { - searchText, - excludedStatuses = new Set(), - closeModal, - confirmButtonContent, - confirmButtonColor, - relationshipAction, - } = props; + const { searchText, excludedStatuses = new Set(), errorMessage } = props; const viewerID = useSelector(state => state.currentUserInfo?.id); const userInfos = useSelector(state => state.userStore.userInfos); const userInfosArray = React.useMemo(() => values(userInfos), [userInfos]); const userStoreSearchIndex = useUserSearchIndex(userInfosArray); const [userStoreSearchResults, setUserStoreSearchResults] = React.useState< $ReadOnlySet, >(new Set(userStoreSearchIndex.getSearchResults(searchText))); React.useEffect(() => { setUserStoreSearchResults( new Set(userStoreSearchIndex.getSearchResults(searchText)), ); }, [searchText, userStoreSearchIndex]); const serverSearchResults = useSearchUsers(searchText); const searchTextPresent = searchText.length > 0; const mergedUserInfos = React.useMemo(() => { const mergedInfos: { [string]: GlobalAccountUserInfo | AccountUserInfo } = {}; for (const userInfo of serverSearchResults) { mergedInfos[userInfo.id] = userInfo; } const userStoreUserIDs = searchTextPresent ? userStoreSearchResults : Object.keys(userInfos); for (const id of userStoreUserIDs) { const { username, relationshipStatus } = userInfos[id]; if (username) { mergedInfos[id] = { id, username, relationshipStatus }; } } return mergedInfos; }, [ searchTextPresent, serverSearchResults, userInfos, userStoreSearchResults, ]); const sortedUsers = React.useMemo( () => Object.keys(mergedUserInfos) .map(userID => mergedUserInfos[userID]) .filter( user => user.id !== viewerID && (!user.relationshipStatus || !excludedStatuses.has(user.relationshipStatus)), ) .sort((user1, user2) => user1.username.localeCompare(user2.username)), [excludedStatuses, mergedUserInfos, viewerID], ); const [pendingUsersToAdd, setPendingUsersToAdd] = React.useState< $ReadOnlyArray, >([]); const selectUser = React.useCallback( (userID: string) => { setPendingUsersToAdd(pendingUsers => { const username = mergedUserInfos[userID]?.username; if (!username || pendingUsers.some(user => user.id === userID)) { return pendingUsers; } const newPendingUser = { id: userID, username, }; let targetIndex = 0; while ( targetIndex < pendingUsers.length && newPendingUser.username.localeCompare( pendingUsers[targetIndex].username, ) > 0 ) { targetIndex++; } return [ ...pendingUsers.slice(0, targetIndex), newPendingUser, ...pendingUsers.slice(targetIndex), ]; }); }, [mergedUserInfos], ); const deselectUser = React.useCallback( (userID: string) => setPendingUsersToAdd(pendingUsers => pendingUsers.filter(userInfo => userInfo.id !== userID), ), [], ); const pendingUserIDs = React.useMemo( () => new Set(pendingUsersToAdd.map(userInfo => userInfo.id)), [pendingUsersToAdd], ); const pendingUsersWithENSNames = useENSNames(pendingUsersToAdd); const userTags = React.useMemo(() => { if (pendingUsersWithENSNames.length === 0) { return null; } const tags = pendingUsersWithENSNames.map(userInfo => ( )); return
{tags}
; }, [deselectUser, pendingUsersWithENSNames]); const filteredUsers = React.useMemo( () => sortedUsers.filter(userInfo => !pendingUserIDs.has(userInfo.id)), [pendingUserIDs, sortedUsers], ); const filteredUsersWithENSNames = useENSNames(filteredUsers); const userRows = React.useMemo( () => filteredUsersWithENSNames.map(userInfo => ( )), [filteredUsersWithENSNames, selectUser], ); - const [errorMessage, setErrorMessage] = React.useState(''); - const callUpdateRelationships = - useLegacyAshoatKeyserverCall(updateRelationships); - const dispatchActionPromise = useDispatchActionPromise(); - const updateRelationshipsPromiseCreator = React.useCallback(async () => { - try { - setErrorMessage(''); - const result = await callUpdateRelationships({ - action: relationshipAction, - userIDs: Array.from(pendingUserIDs), - }); - closeModal(); - return result; - } catch (e) { - setErrorMessage('unknown error'); - throw e; - } - }, [callUpdateRelationships, closeModal, pendingUserIDs, relationshipAction]); - const confirmSelection = React.useCallback( - () => - dispatchActionPromise( - updateRelationshipsActionTypes, - updateRelationshipsPromiseCreator(), - ), - [dispatchActionPromise, updateRelationshipsPromiseCreator], - ); - const loadingStatus = useSelector(loadingStatusSelector); - let buttonContent = confirmButtonContent; - if (loadingStatus === 'loading') { - buttonContent = ( - <> -
{confirmButtonContent}
- - - ); - } - let errors; if (errorMessage) { errors =
{errorMessage}
; } return (
{userTags}
{userRows}
{errors} -
- - -
); } export default AddUsersList;