diff --git a/app/definitions/IProfile.ts b/app/definitions/IProfile.ts new file mode 100644 index 000000000..f75885c09 --- /dev/null +++ b/app/definitions/IProfile.ts @@ -0,0 +1,31 @@ +import React from 'react'; + +export interface IProfileParams { + name: string; + username: string; + email: string | null; + newPassword: string; + currentPassword: string; +} + +export interface IAvatarButton { + key: string; + child: React.ReactNode; + onPress: () => void; + disabled: boolean; +} + +export interface IAvatar { + data: {} | string | null; + url?: string; + contentType?: string; + service?: any; +} + +export interface IAvatarSuggestion { + [service: string]: { + url: string; + blob: string; + contentType: string; + }; +} diff --git a/app/definitions/IProfileViewInterfaces.ts b/app/definitions/IProfileViewInterfaces.ts deleted file mode 100644 index ce41a1e9e..000000000 --- a/app/definitions/IProfileViewInterfaces.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { StackNavigationProp } from '@react-navigation/stack'; -import React from 'react'; - -import { TSupportedThemes } from '../theme'; -import { ProfileStackParamList } from '../stacks/types'; -import { IUser } from './IUser'; - -export interface IParams { - name: string; - username: string; - email: string | null; - newPassword: string; - currentPassword: string; -} - -export interface IAvatarButton { - key: string; - child: React.ReactNode; - onPress: () => void; - disabled: boolean; -} - -export interface INavigationOptions { - navigation: StackNavigationProp; - isMasterDetail?: boolean; -} - -export interface IProfileViewProps { - user: IUser; - baseUrl: string; - Accounts_AllowEmailChange: boolean; - Accounts_AllowPasswordChange: boolean; - Accounts_AllowRealNameChange: boolean; - Accounts_AllowUserAvatarChange: boolean; - Accounts_AllowUsernameChange: boolean; - Accounts_CustomFields: string; - setUser: Function; - theme: TSupportedThemes; -} - -export interface IAvatar { - data: {} | string | null; - url?: string; - contentType?: string; - service?: any; -} - -export interface IAvatarSuggestion { - [service: string]: { - url: string; - blob: string; - contentType: string; - }; -} - -export interface IProfileViewState { - saving: boolean; - name: string; - username: string; - email: string | null; - newPassword: string | null; - currentPassword: string | null; - avatarUrl: string | null; - avatar: IAvatar; - avatarSuggestions: IAvatarSuggestion; - customFields: { - [key: string | number]: string; - }; -} diff --git a/app/definitions/index.ts b/app/definitions/index.ts index 8576c9493..a0d9d3ea9 100644 --- a/app/definitions/index.ts +++ b/app/definitions/index.ts @@ -28,6 +28,7 @@ export * from './IUrl'; export * from './ICredentials'; export * from './ISearch'; export * from './TUserStatus'; +export * from './IProfile'; export interface IBaseScreen, S extends string> { navigation: StackNavigationProp; diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index 898799102..9015fd349 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -1,4 +1,4 @@ -import { IParams } from '../../IProfileViewInterfaces'; +import { IProfileParams } from '../../IProfile'; import type { ITeam } from '../../ITeam'; import type { IUser } from '../../IUser'; import { INotificationPreferences, IUserPreferences, IUserRegistered } from '../../IUser'; @@ -39,7 +39,10 @@ export type UsersEndpoints = { POST: (params: { status: string; message: string }) => {}; }; 'users.updateOwnBasicInfo': { - POST: (params: { data: IParams | Pick; customFields?: { [key: string | number]: string } }) => { + POST: (params: { + data: IProfileParams | Pick; + customFields?: { [key: string | number]: string }; + }) => { user: IUser; }; }; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 44e361589..502220e9d 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -5,9 +5,10 @@ import { IRoom, IRoomNotifications, SubscriptionType, - IUser + IUser, + IAvatarSuggestion, + IProfileParams } from '../../definitions'; -import { IAvatarSuggestion, IParams } from '../../definitions/IProfileViewInterfaces'; import { ISpotlight } from '../../definitions/ISpotlight'; import { TEAM_TYPE } from '../../definitions/ITeam'; import { Encryption } from '../encryption'; @@ -561,7 +562,10 @@ export const saveRoomSettings = ( // RC 0.55.0 sdk.methodCallWrapper('saveRoomSettings', rid, params); -export const saveUserProfile = (data: IParams | Pick, customFields?: { [key: string | number]: string }) => +export const saveUserProfile = ( + data: IProfileParams | Pick, + customFields?: { [key: string | number]: string } +) => // RC 0.62.2 sdk.post('users.updateOwnBasicInfo', { data, customFields }); diff --git a/app/views/ProfileView/index.tsx b/app/views/ProfileView/index.tsx index e5bdfad2a..f27ed08f9 100644 --- a/app/views/ProfileView/index.tsx +++ b/app/views/ProfileView/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Keyboard, ScrollView, View } from 'react-native'; +import { Keyboard, ScrollView, TextInput, View } from 'react-native'; import { connect } from 'react-redux'; import prompt from 'react-native-prompt-android'; import { sha256 } from 'js-sha256'; @@ -21,34 +21,62 @@ import log, { events, logEvent } from '../../utils/log'; import I18n from '../../i18n'; import Button from '../../containers/Button'; import Avatar from '../../containers/Avatar'; -import { setUser as setUserAction } from '../../actions/login'; +import { setUser } from '../../actions/login'; import { CustomIcon } from '../../containers/CustomIcon'; import * as HeaderButton from '../../containers/HeaderButton'; import StatusBar from '../../containers/StatusBar'; import { themes } from '../../lib/constants'; -import { withTheme } from '../../theme'; +import { TSupportedThemes, withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; import styles from './styles'; +import { ProfileStackParamList } from '../../stacks/types'; +import { Services } from '../../lib/services'; import { + IApplicationState, IAvatar, IAvatarButton, - INavigationOptions, - IParams, - IProfileViewProps, - IProfileViewState -} from '../../definitions/IProfileViewInterfaces'; -import { IUser } from '../../definitions'; -import { Services } from '../../lib/services'; + IAvatarSuggestion, + IBaseScreen, + IProfileParams, + IUser +} from '../../definitions'; + +interface IProfileViewProps extends IBaseScreen { + user: IUser; + baseUrl: string; + Accounts_AllowEmailChange: boolean; + Accounts_AllowPasswordChange: boolean; + Accounts_AllowRealNameChange: boolean; + Accounts_AllowUserAvatarChange: boolean; + Accounts_AllowUsernameChange: boolean; + Accounts_CustomFields: string; + theme: TSupportedThemes; +} + +interface IProfileViewState { + saving: boolean; + name: string; + username: string; + email: string | null; + newPassword: string | null; + currentPassword: string | null; + avatarUrl: string | null; + avatar: IAvatar; + avatarSuggestions: IAvatarSuggestion; + customFields: { + [key: string | number]: string; + }; +} class ProfileView extends React.Component { - private name: any; - private username: any; - private email: any; - private avatarUrl: any; - private newPassword: any; + private name?: TextInput | null; + private username?: TextInput | null; + private email?: TextInput; + private avatarUrl?: TextInput; + private newPassword?: TextInput; - static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions) => { + static navigationOptions = ({ navigation, isMasterDetail }: IProfileViewProps) => { const options: StackNavigationOptions = { title: I18n.t('Profile') }; @@ -150,7 +178,7 @@ class ProfileView extends React.Component !newPassword && user.emails && user.emails[0].address === email && - !avatar!.data && + !avatar.data && !customFieldsChanged ); }; @@ -172,8 +200,8 @@ class ProfileView extends React.Component this.setState({ saving: true }); const { name, username, email, newPassword, currentPassword, avatar, customFields } = this.state; - const { user, setUser } = this.props; - const params = {} as IParams; + const { user, dispatch } = this.props; + const params = {} as IProfileParams; // Name if (user.name !== name) { @@ -225,7 +253,7 @@ class ProfileView extends React.Component } try { - if (avatar!.url) { + if (avatar.url) { try { logEvent(events.PROFILE_SAVE_AVATAR); await Services.setAvatarFromService(avatar); @@ -241,9 +269,9 @@ class ProfileView extends React.Component if (result.success) { logEvent(events.PROFILE_SAVE_CHANGES); if (customFields) { - setUser({ customFields, ...params }); + dispatch(setUser({ customFields, ...params })); } else { - setUser({ ...params }); + dispatch(setUser({ ...params })); } EventEmitter.emit(LISTENER, { message: I18n.t('Profile_saved_successfully') }); this.init(); @@ -339,7 +367,7 @@ class ProfileView extends React.Component })} {this.renderAvatarButton({ child: , - onPress: () => this.pickImageWithURL(avatarUrl!), + onPress: () => (avatarUrl ? this.pickImageWithURL(avatarUrl) : null), disabled: !avatarUrl, key: 'profile-view-avatar-url-button' })} @@ -419,7 +447,7 @@ class ProfileView extends React.Component // @ts-ignore return this[array[index + 1]].focus(); } - this.avatarUrl.focus(); + this.avatarUrl?.focus(); }} theme={theme} /> @@ -482,7 +510,7 @@ class ProfileView extends React.Component value={name} onChangeText={(value: string) => this.setState({ name: value })} onSubmitEditing={() => { - this.username.focus(); + this.username?.focus(); }} testID='profile-view-name' theme={theme} @@ -498,7 +526,7 @@ class ProfileView extends React.Component value={username} onChangeText={value => this.setState({ username: value })} onSubmitEditing={() => { - this.email.focus(); + this.email?.focus(); }} testID='profile-view-username' theme={theme} @@ -507,14 +535,16 @@ class ProfileView extends React.Component editable={Accounts_AllowEmailChange} inputStyle={[!Accounts_AllowEmailChange && styles.disabled]} inputRef={e => { - this.email = e; + if (e) { + this.email = e; + } }} label={I18n.t('Email')} placeholder={I18n.t('Email')} - value={email!} + value={email || undefined} onChangeText={value => this.setState({ email: value })} onSubmitEditing={() => { - this.newPassword.focus(); + this.newPassword?.focus(); }} testID='profile-view-email' theme={theme} @@ -523,18 +553,20 @@ class ProfileView extends React.Component editable={Accounts_AllowPasswordChange} inputStyle={[!Accounts_AllowPasswordChange && styles.disabled]} inputRef={e => { - this.newPassword = e; + if (e) { + this.newPassword = e; + } }} label={I18n.t('New_Password')} placeholder={I18n.t('New_Password')} - value={newPassword!} + value={newPassword || undefined} onChangeText={value => this.setState({ newPassword: value })} onSubmitEditing={() => { if (Accounts_CustomFields && Object.keys(customFields).length) { // @ts-ignore return this[Object.keys(customFields)[0]].focus(); } - this.avatarUrl.focus(); + this.avatarUrl?.focus(); }} secureTextEntry testID='profile-view-new-password' @@ -545,11 +577,13 @@ class ProfileView extends React.Component editable={Accounts_AllowUserAvatarChange} inputStyle={[!Accounts_AllowUserAvatarChange && styles.disabled]} inputRef={e => { - this.avatarUrl = e; + if (e) { + this.avatarUrl = e; + } }} label={I18n.t('Avatar_Url')} placeholder={I18n.t('Avatar_Url')} - value={avatarUrl!} + value={avatarUrl || undefined} onChangeText={value => this.setState({ avatarUrl: value })} onSubmitEditing={this.submit} testID='profile-view-avatar-url' @@ -580,19 +614,15 @@ class ProfileView extends React.Component } } -const mapStateToProps = (state: any) => ({ +const mapStateToProps = (state: IApplicationState) => ({ user: getUserSelector(state), - Accounts_AllowEmailChange: state.settings.Accounts_AllowEmailChange, - Accounts_AllowPasswordChange: state.settings.Accounts_AllowPasswordChange, - Accounts_AllowRealNameChange: state.settings.Accounts_AllowRealNameChange, - Accounts_AllowUserAvatarChange: state.settings.Accounts_AllowUserAvatarChange, - Accounts_AllowUsernameChange: state.settings.Accounts_AllowUsernameChange, - Accounts_CustomFields: state.settings.Accounts_CustomFields, + Accounts_AllowEmailChange: state.settings.Accounts_AllowEmailChange as boolean, + Accounts_AllowPasswordChange: state.settings.Accounts_AllowPasswordChange as boolean, + Accounts_AllowRealNameChange: state.settings.Accounts_AllowRealNameChange as boolean, + Accounts_AllowUserAvatarChange: state.settings.Accounts_AllowUserAvatarChange as boolean, + Accounts_AllowUsernameChange: state.settings.Accounts_AllowUsernameChange as boolean, + Accounts_CustomFields: state.settings.Accounts_CustomFields as string, baseUrl: state.server.server }); -const mapDispatchToProps = (dispatch: any) => ({ - setUser: (params: any) => dispatch(setUserAction(params)) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(withTheme(ProfileView)); +export default connect(mapStateToProps)(withTheme(ProfileView)); diff --git a/app/views/RoomInfoEditView/index.tsx b/app/views/RoomInfoEditView/index.tsx index 31822ce27..578f479a3 100644 --- a/app/views/RoomInfoEditView/index.tsx +++ b/app/views/RoomInfoEditView/index.tsx @@ -16,7 +16,7 @@ import StatusBar from '../../containers/StatusBar'; import RCTextInput from '../../containers/TextInput'; import { LISTENER } from '../../containers/Toast'; import { MultiSelect } from '../../containers/UIKit/MultiSelect'; -import { IApplicationState, IBaseScreen, ISubscription, SubscriptionType, TSubscriptionModel } from '../../definitions'; +import { IApplicationState, IBaseScreen, ISubscription, SubscriptionType, TSubscriptionModel, IAvatar } from '../../definitions'; import { ERoomType } from '../../definitions/ERoomType'; import I18n from '../../i18n'; import database from '../../lib/database'; @@ -32,7 +32,6 @@ import log, { events, logEvent } from '../../utils/log'; import { MessageTypeValues } from '../../utils/messageTypes'; import random from '../../utils/random'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; -import { IAvatar } from '../../definitions/IProfileViewInterfaces'; import sharedStyles from '../Styles'; import styles from './styles'; import SwitchContainer from './SwitchContainer';