diff --git a/web/avatars/emoji-avatar-selection-modal.css b/web/avatars/emoji-avatar-selection-modal.css index a1abe7ac0..adb5f27b4 100644 --- a/web/avatars/emoji-avatar-selection-modal.css +++ b/web/avatars/emoji-avatar-selection-modal.css @@ -1,27 +1,34 @@ div.modalBody { display: flex; flex-direction: column; overflow: hidden; padding: 20px; } div.avatarContainer { display: flex; justify-content: center; padding-top: 20px; } div.emojiPickerContainer { display: flex; justify-content: center; align-items: center; } div.colorSelectorContainer { margin: 20px 60px; } div.saveButtonContainer { display: flex; flex-direction: column; } + +div.saveAvatarButtonContent { + display: flex; + justify-content: center; + align-items: center; + min-height: 24px; +} diff --git a/web/avatars/emoji-avatar-selection-modal.react.js b/web/avatars/emoji-avatar-selection-modal.react.js index 0fe491d27..c0b299ed8 100644 --- a/web/avatars/emoji-avatar-selection-modal.react.js +++ b/web/avatars/emoji-avatar-selection-modal.react.js @@ -1,101 +1,112 @@ // @flow import data from '@emoji-mart/data'; import Picker from '@emoji-mart/react'; import invariant from 'invariant'; import * as React from 'react'; import { EditUserAvatarContext } from 'lib/components/base-edit-user-avatar-provider.react.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { defaultAnonymousUserEmojiAvatar, getAvatarForUser, getDefaultAvatar, } from 'lib/shared/avatar-utils.js'; import type { ClientAvatar, ClientEmojiAvatar, } from 'lib/types/avatar-types.js'; import Avatar from './avatar.react.js'; import css from './emoji-avatar-selection-modal.css'; import Button, { buttonThemes } from '../components/button.react.js'; +import LoadingIndicator from '../loading-indicator.react.js'; import Modal from '../modals/modal.react.js'; import ColorSelector from '../modals/threads/color-selector.react.js'; import { useSelector } from '../redux/redux-utils.js'; function EmojiAvatarSelectionModal(): React.Node { const { popModal } = useModalContext(); const editUserAvatarContext = React.useContext(EditUserAvatarContext); invariant(editUserAvatarContext, 'editUserAvatarContext should be set'); - const { setUserAvatar } = editUserAvatarContext; + const { setUserAvatar, userAvatarSaveInProgress } = editUserAvatarContext; const currentUserInfo = useSelector(state => state.currentUserInfo); const currentUserAvatar: ClientAvatar = getAvatarForUser(currentUserInfo); const defaultUserAvatar: ClientEmojiAvatar = currentUserInfo?.username ? getDefaultAvatar(currentUserInfo.username) : defaultAnonymousUserEmojiAvatar; // eslint-disable-next-line no-unused-vars const [pendingAvatarEmoji, setPendingAvatarEmoji] = React.useState( currentUserAvatar.type === 'emoji' ? currentUserAvatar.emoji : defaultUserAvatar.emoji, ); const [pendingAvatarColor, setPendingAvatarColor] = React.useState( currentUserAvatar.type === 'emoji' ? currentUserAvatar.color : defaultUserAvatar.color, ); const pendingEmojiAvatar: ClientEmojiAvatar = React.useMemo( () => ({ type: 'emoji', emoji: pendingAvatarEmoji, color: pendingAvatarColor, }), [pendingAvatarColor, pendingAvatarEmoji], ); const onEmojiSelect = React.useCallback(selection => { setPendingAvatarEmoji(selection.native); }, []); const onSaveAvatar = React.useCallback( () => setUserAvatar(pendingEmojiAvatar), [pendingEmojiAvatar, setUserAvatar], ); + let saveButtonContent; + if (userAvatarSaveInProgress) { + saveButtonContent = ; + } else { + saveButtonContent = 'Save Avatar'; + } + return ( - Save Avatar + + {saveButtonContent} + ); } export default EmojiAvatarSelectionModal; diff --git a/web/sidebar/community-creation/community-creation-modal.react.js b/web/sidebar/community-creation/community-creation-modal.react.js index 3bd02e609..072e0b32d 100644 --- a/web/sidebar/community-creation/community-creation-modal.react.js +++ b/web/sidebar/community-creation/community-creation-modal.react.js @@ -1,208 +1,209 @@ // @flow import * as React from 'react'; import { useDispatch } from 'react-redux'; import { newThread, newThreadActionTypes } from 'lib/actions/thread-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import type { NewThreadResult } from 'lib/types/thread-types.js'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import CommunityCreationKeyserverLabel from './community-creation-keyserver-label.react.js'; import CommunityCreationMembersModal from './community-creation-members-modal.react.js'; import css from './community-creation-modal.css'; import UserAvatar from '../../avatars/user-avatar.react.js'; import CommIcon from '../../CommIcon.react.js'; import Button, { buttonThemes } from '../../components/button.react.js'; import EnumSettingsOption from '../../components/enum-settings-option.react.js'; import LoadingIndicator from '../../loading-indicator.react.js'; import Input from '../../modals/input.react.js'; import Modal from '../../modals/modal.react.js'; import { updateNavInfoActionType } from '../../redux/action-types.js'; import { useSelector } from '../../redux/redux-utils.js'; import { nonThreadCalendarQuery } from '../../selectors/nav-selectors.js'; const announcementStatements = [ { statement: `This option sets the community’s root channel to an ` + `announcement channel. Only admins and other admin-appointed ` + `roles can send messages in an announcement channel.`, isStatementValid: true, styleStatementBasedOnValidity: false, }, ]; const createNewCommunityLoadingStatusSelector = createLoadingStatusSelector(newThreadActionTypes); function CommunityCreationModal(): React.Node { const modalContext = useModalContext(); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const callNewThread = useServerCall(newThread); const calendarQueryFunc = useSelector(nonThreadCalendarQuery); const [errorMessage, setErrorMessage] = React.useState(); const [pendingCommunityName, setPendingCommunityName] = React.useState(''); const onChangePendingCommunityName = React.useCallback( (event: SyntheticEvent) => { setErrorMessage(); setPendingCommunityName(event.currentTarget.value); }, [], ); const [announcementSetting, setAnnouncementSetting] = React.useState(false); const onAnnouncementSelected = React.useCallback(() => { setErrorMessage(); setAnnouncementSetting(!announcementSetting); }, [announcementSetting]); const callCreateNewCommunity = React.useCallback(async () => { const calendarQuery = calendarQueryFunc(); try { const newThreadResult: NewThreadResult = await callNewThread({ name: pendingCommunityName, type: announcementSetting ? threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT : threadTypes.COMMUNITY_ROOT, calendarQuery, }); return newThreadResult; } catch (e) { setErrorMessage('Community creation failed. Please try again.'); throw e; } }, [ announcementSetting, calendarQueryFunc, callNewThread, pendingCommunityName, ]); const createNewCommunity = React.useCallback(async () => { setErrorMessage(); const newThreadResultPromise = callCreateNewCommunity(); dispatchActionPromise(newThreadActionTypes, newThreadResultPromise); const newThreadResult: NewThreadResult = await newThreadResultPromise; const { newThreadID } = newThreadResult; await dispatch({ type: updateNavInfoActionType, payload: { activeChatThreadID: newThreadID, }, }); modalContext.popModal(); modalContext.pushModal( , ); }, [callCreateNewCommunity, dispatch, dispatchActionPromise, modalContext]); const megaphoneIcon = React.useMemo( () => , [], ); const avatarNodeEnabled = false; let avatarNode; if (avatarNodeEnabled) { avatarNode = ( ); } const createNewCommunityLoadingStatus: LoadingStatus = useSelector( createNewCommunityLoadingStatusSelector, ); let buttonContent; if (createNewCommunityLoadingStatus === 'loading') { buttonContent = ( ); } else if (errorMessage) { buttonContent = errorMessage; } else { buttonContent = 'Create community'; } return ( {avatarNode} Community Name You may edit your community’s image and name later. Optional settings {buttonContent} ); } export default CommunityCreationModal;