diff --git a/web/settings/account-settings.css b/web/settings/account-settings.css index 47adf83f4..28daa7bbf 100644 --- a/web/settings/account-settings.css +++ b/web/settings/account-settings.css @@ -1,81 +1,67 @@ .container { flex: 1; background-color: var(--frame-background-primary-default); overflow: auto; } .contentContainer { padding: 40px; width: 456px; overflow-y: auto; } .header { color: var(--text-background-primary-default); font-weight: var(--semi-bold); line-height: var(--line-height-display); padding-bottom: 55px; } .content { margin-top: 32px; } .content ul { list-style-type: none; } .content li { color: var(--text-background-tertiary-default); padding: 24px 16px 16px; display: flex; flex-direction: row; justify-content: space-between; } .content li:not(:last-child) { border-bottom: 1px solid var(--separator-background-primary-default); } .logoutContainer { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .logoutLabel { color: var(--text-background-tertiary-default); } .username { color: var(--text-background-primary-default); } .buttonText { color: var(--link-background-primary-default); line-height: var(--line-height-text); } -.passwordContainer { - display: flex; -} - -.password { - align-items: center; - padding-right: 16px; -} - -.editPasswordLink { - color: var(--text-background-tertiary-default); - cursor: pointer; -} - .preferencesContainer { padding-top: 24px; } .preferencesHeader { color: var(--text-background-primary-default); font-weight: var(--semi-bold); line-height: var(--line-height-display); } diff --git a/web/settings/account-settings.react.js b/web/settings/account-settings.react.js index 28abe8225..c9539f9b5 100644 --- a/web/settings/account-settings.react.js +++ b/web/settings/account-settings.react.js @@ -1,365 +1,338 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import uuid from 'uuid'; import { useLogOut, logOutActionTypes, useSecondaryDeviceLogOut, } from 'lib/actions/user-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; import { useStringForUser } from 'lib/hooks/ens-cache.js'; -import { accountHasPassword } from 'lib/shared/account-utils.js'; import { dmOperationSpecificationTypes, type OutboundDMOperationSpecification, } from 'lib/shared/dm-ops/dm-op-utils.js'; import { useProcessAndSendDMOperation } from 'lib/shared/dm-ops/process-dm-ops.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; import type { DMCreateThreadOperation } from 'lib/types/dm-ops.js'; import { thickThreadTypes } from 'lib/types/thread-types-enum.js'; import { createOlmSessionsWithOwnDevices, getContentSigningKey, } from 'lib/utils/crypto-utils.js'; import { isDev } from 'lib/utils/dev-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import css from './account-settings.css'; import AppearanceChangeModal from './appearance-change-modal.react.js'; import BackupTestRestoreModal from './backup-test-restore-modal.react.js'; -import PasswordChangeModal from './password-change-modal.js'; import BlockListModal from './relationship/block-list-modal.react.js'; import FriendListModal from './relationship/friend-list-modal.react.js'; import TunnelbrokerMessagesScreen from './tunnelbroker-message-list.react.js'; import TunnelbrokerTestScreen from './tunnelbroker-test.react.js'; import EditUserAvatar from '../avatars/edit-user-avatar.react.js'; import Button from '../components/button.react.js'; import VersionUnsupportedModal from '../modals/version-unsupported-modal.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; function AccountSettings(): React.Node { const { pushModal, popModal } = useModalContext(); const logOutOptions = React.useMemo(() => { const showVersionUnsupportedModal = () => { pushModal(); }; return { handleUseNewFlowResponse: showVersionUnsupportedModal }; }, [pushModal]); const sendLogoutRequest = useLogOut(logOutOptions); const sendSecondaryDeviceLogoutRequest = useSecondaryDeviceLogOut(); const dispatchActionPromise = useDispatchActionPromise(); const logOutUser = React.useCallback( () => dispatchActionPromise(logOutActionTypes, sendLogoutRequest()), [dispatchActionPromise, sendLogoutRequest], ); const logOutSecondaryDevice = React.useCallback( () => dispatchActionPromise( logOutActionTypes, sendSecondaryDeviceLogoutRequest(), ), [dispatchActionPromise, sendSecondaryDeviceLogoutRequest], ); const identityContext = React.useContext(IdentityClientContext); const userID = useSelector(state => state.currentUserInfo?.id); const [deviceID, setDeviceID] = React.useState(); React.useEffect(() => { void (async () => { const contentSigningKey = await getContentSigningKey(); setDeviceID(contentSigningKey); })(); }, []); - const showPasswordChangeModal = React.useCallback( - () => pushModal(), - [pushModal], - ); - const openFriendList = React.useCallback( () => pushModal(), [pushModal], ); const openBlockList = React.useCallback( () => pushModal(), [pushModal], ); - const isAccountWithPassword = useSelector(state => - accountHasPassword(state.currentUserInfo), - ); - const currentUserInfo = useSelector(state => state.currentUserInfo); const stringForUser = useStringForUser(currentUserInfo); const staffCanSee = useStaffCanSee(); const { sendMessageToDevice, socketState, addListener, removeListener } = useTunnelbroker(); const openTunnelbrokerModal = React.useCallback( () => pushModal( , ), [popModal, pushModal, sendMessageToDevice], ); const openTunnelbrokerMessagesModal = React.useCallback( () => pushModal( , ), [addListener, popModal, pushModal, removeListener], ); const onCreateOlmSessions = React.useCallback(async () => { if (!identityContext) { return; } const authMetadata = await identityContext.getAuthMetadata(); try { await createOlmSessionsWithOwnDevices( authMetadata, identityContext.identityClient, sendMessageToDevice, ); } catch (e) { console.log(`Error creating olm sessions with own devices: ${e.message}`); } }, [identityContext, sendMessageToDevice]); const openBackupTestRestoreModal = React.useCallback( () => pushModal(), [popModal, pushModal], ); const processAndSendDMOperation = useProcessAndSendDMOperation(); const onCreateDMThread = React.useCallback(async () => { invariant(userID, 'userID should be set'); const op: DMCreateThreadOperation = { type: 'create_thread', threadID: uuid.v4(), creatorID: userID, time: Date.now(), threadType: thickThreadTypes.LOCAL, memberIDs: [], roleID: uuid.v4(), newMessageID: uuid.v4(), }; const specification: OutboundDMOperationSpecification = { type: dmOperationSpecificationTypes.OUTBOUND, op, recipients: { type: 'self_devices', }, }; await processAndSendDMOperation(specification); }, [processAndSendDMOperation, userID]); const showAppearanceModal = React.useCallback( () => pushModal(), [pushModal], ); if (!currentUserInfo || currentUserInfo.anonymous) { return null; } - let changePasswordSection; - if (isAccountWithPassword) { - changePasswordSection = ( -
  • - Password - - ****** - - - - -
  • - ); - } - let experimentalLogOutSection; if (isDev) { experimentalLogOutSection = (
  • Log out secondary device
  • ); } let preferences; if (staffCanSee) { preferences = (

    Preferences

    • Appearance
    ); } let tunnelbroker; if (staffCanSee) { tunnelbroker = (

    Tunnelbroker menu

    • Connected {socketState.connected.toString()}
    • Send message to device
    • Trace received messages
    • Create session with own devices
    ); } let backup; if (staffCanSee) { backup = (

    Backup menu

    • Test backup restore
    ); } let deviceData; if (staffCanSee) { deviceData = (

    Device ID

    • {deviceID}

    User ID

    • {userID}
    ); } let dms; if (staffCanSee) { dms = (

    DMs menu

    • Create a new local DM thread
    ); } return (

    My Account

    • {'Logged in as '} {stringForUser}

    • {experimentalLogOutSection} - {changePasswordSection}
    • Friend List
    • Block List
    {preferences} {tunnelbroker} {backup} {deviceData} {dms}
    ); } export default AccountSettings; diff --git a/web/settings/password-change-modal.css b/web/settings/password-change-modal.css deleted file mode 100644 index bcdd0fb55..000000000 --- a/web/settings/password-change-modal.css +++ /dev/null @@ -1,38 +0,0 @@ -.modalFormError { - font-size: var(--xs-font-12); - color: var(--text-background-danger-default); - font-style: italic; - height: 24px; - display: flex; - justify-content: center; - align-items: flex-end; -} - -.errorPlaceholder { - height: 24px; -} - -.formContent { - display: flex; - flex-direction: column; -} -.formContent input:not(:last-child) { - margin-bottom: 16px; -} - -.formContent input[type='password'] { - width: auto; -} - -.usernameContainer { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-bottom: 16px; -} -.usernameLabel { - color: var(--text-background-tertiary-default); -} -.username { - color: var(--text-background-primary-default); -} diff --git a/web/settings/password-change-modal.js b/web/settings/password-change-modal.js deleted file mode 100644 index 59a041416..000000000 --- a/web/settings/password-change-modal.js +++ /dev/null @@ -1,296 +0,0 @@ -// @flow - -import invariant from 'invariant'; -import * as React from 'react'; - -import { - changeKeyserverUserPasswordActionTypes, - changeKeyserverUserPassword, - useChangeIdentityUserPassword, - changeIdentityUserPasswordActionTypes, -} from 'lib/actions/user-actions.js'; -import { useModalContext } from 'lib/components/modal-provider.react.js'; -import { useStringForUser } from 'lib/hooks/ens-cache.js'; -import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js'; -import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; -import { type PasswordUpdate } from 'lib/types/user-types.js'; -import { getMessageForException } from 'lib/utils/errors.js'; -import { - useDispatchActionPromise, - type DispatchActionPromise, -} from 'lib/utils/redux-promise-utils.js'; -import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; - -import css from './password-change-modal.css'; -import Button from '../components/button.react.js'; -import Input from '../modals/input.react.js'; -import Modal from '../modals/modal.react.js'; -import { useSelector } from '../redux/redux-utils.js'; - -type Props = { - +inputDisabled: boolean, - +dispatchActionPromise: DispatchActionPromise, - +changeKeyserverUserPassword: ( - passwordUpdate: PasswordUpdate, - ) => Promise, - +changeIdentityUserPassword: ( - oldPassword: string, - newPassword: string, - ) => Promise, - +popModal: () => void, - +stringForUser: ?string, -}; -type State = { - +newPassword: string, - +confirmNewPassword: string, - +currentPassword: string, - +errorMessage: string, -}; - -class PasswordChangeModal extends React.PureComponent { - newPasswordInput: ?HTMLInputElement; - currentPasswordInput: ?HTMLInputElement; - - constructor(props: Props) { - super(props); - this.state = { - newPassword: '', - confirmNewPassword: '', - currentPassword: '', - errorMessage: '', - }; - } - - componentDidMount() { - invariant(this.newPasswordInput, 'newPasswordInput ref unset'); - this.newPasswordInput.focus(); - } - - render(): React.Node { - const { inputDisabled } = this.props; - - let errorMsg =
    ; - if (this.state.errorMessage) { - errorMsg = ( -
    {this.state.errorMessage}
    - ); - } - - const buttonIsDisabled = - inputDisabled || - this.state.currentPassword.length === 0 || - this.state.newPassword.length === 0 || - this.state.confirmNewPassword.length === 0; - - const changePasswordButton = ( - - ); - - return ( - -
    -
    -

    - {'Logged in as '} - {this.props.stringForUser} -

    - - - -
    - {errorMsg} -
    -
    - ); - } - - newPasswordInputRef = (newPasswordInput: ?HTMLInputElement) => { - this.newPasswordInput = newPasswordInput; - }; - - currentPasswordInputRef = (currentPasswordInput: ?HTMLInputElement) => { - this.currentPasswordInput = currentPasswordInput; - }; - - onChangeNewPassword = (event: SyntheticEvent) => { - const target = event.target; - invariant(target instanceof HTMLInputElement, 'target not input'); - this.setState({ newPassword: target.value }); - }; - - onChangeConfirmNewPassword = (event: SyntheticEvent) => { - const target = event.target; - invariant(target instanceof HTMLInputElement, 'target not input'); - this.setState({ confirmNewPassword: target.value }); - }; - - onChangeCurrentPassword = (event: SyntheticEvent) => { - const target = event.target; - invariant(target instanceof HTMLInputElement, 'target not input'); - this.setState({ currentPassword: target.value }); - }; - - onSubmit = (event: SyntheticEvent) => { - event.preventDefault(); - - if (this.state.newPassword === '') { - this.setState( - { - newPassword: '', - confirmNewPassword: '', - errorMessage: 'empty password', - }, - () => { - invariant(this.newPasswordInput, 'newPasswordInput ref unset'); - this.newPasswordInput.focus(); - }, - ); - } else if (this.state.newPassword !== this.state.confirmNewPassword) { - this.setState( - { - newPassword: '', - confirmNewPassword: '', - errorMessage: 'passwords don’t match', - }, - () => { - invariant(this.newPasswordInput, 'newPasswordInput ref unset'); - this.newPasswordInput.focus(); - }, - ); - return; - } - - if (usingCommServicesAccessToken) { - void this.props.dispatchActionPromise( - changeIdentityUserPasswordActionTypes, - this.changeUserSettingsAction(), - ); - } else { - void this.props.dispatchActionPromise( - changeKeyserverUserPasswordActionTypes, - this.changeUserSettingsAction(), - ); - } - }; - - async changeUserSettingsAction(): Promise { - try { - if (usingCommServicesAccessToken) { - await this.props.changeIdentityUserPassword( - this.state.currentPassword, - this.state.newPassword, - ); - } else { - await this.props.changeKeyserverUserPassword({ - updatedFields: { - password: this.state.newPassword, - }, - currentPassword: this.state.currentPassword, - }); - } - this.props.popModal(); - } catch (e) { - const messageForException = getMessageForException(e); - if ( - messageForException === 'invalid_credentials' || - messageForException === 'login_failed' - ) { - this.setState( - { - currentPassword: '', - errorMessage: 'wrong current password', - }, - () => { - invariant( - this.currentPasswordInput, - 'currentPasswordInput ref unset', - ); - this.currentPasswordInput.focus(); - }, - ); - } else { - this.setState( - { - newPassword: '', - confirmNewPassword: '', - currentPassword: '', - errorMessage: 'unknown error', - }, - () => { - invariant(this.newPasswordInput, 'newPasswordInput ref unset'); - this.newPasswordInput.focus(); - }, - ); - } - throw e; - } - } -} - -const changeUserPasswordLoadingStatusSelector = usingCommServicesAccessToken - ? createLoadingStatusSelector(changeIdentityUserPasswordActionTypes) - : createLoadingStatusSelector(changeKeyserverUserPasswordActionTypes); -const ConnectedPasswordChangeModal: React.ComponentType<{}> = React.memo<{}>( - function ConnectedPasswordChangeModal(): React.Node { - const inputDisabled = useSelector( - state => changeUserPasswordLoadingStatusSelector(state) === 'loading', - ); - const callChangeKeyserverUserPassword = useLegacyAshoatKeyserverCall( - changeKeyserverUserPassword, - ); - const callChangeIdentityUserPassword = useChangeIdentityUserPassword(); - const dispatchActionPromise = useDispatchActionPromise(); - - const modalContext = useModalContext(); - - const currentUserInfo = useSelector(state => state.currentUserInfo); - const stringForUser = useStringForUser(currentUserInfo); - - return ( - - ); - }, -); - -export default ConnectedPasswordChangeModal;