* feature edit profile screen * minor refactoring * Apply suggestions from code review Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * ts fixes * revert floatingTextInput label This reverts commit a778e1f76191aea7c1a18d60a23ffbd6d3dec0eb. * code clean up * Apply suggestions from code review Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * code fix * code fix * Adding preventDoubleTap * rename id to fieldKey * Update edit_profile.tsx * wip * navigating through fields; partly done * navigating through fields - partly done * navigating through fields; partly done * completed field navigation * added theme into dependency array * code clean up * revert conditions for disabling fields * Added colorFilters prop to Loading component * Completed loading feature on Edit Profile screen * code clean up * Add profile_error * renamed valid_mime_types to valid_image_mime_types * added props isDisabled to email field * refactored next field logic * fix * fix * code clean up * code clean up * Updated ESLINT hook rules to warning instead of disabled * code fix * code fix * new line within your_profile component * added memo for Field component * added canSave to dependency array * update loading component - color filter * Update app/screens/edit_profile/edit_profile.tsx Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * dependency fix * fix to fetch my latest status * fix to remove unnecessary user id for local action updateLocalUser * prevents bouncing for iOS * code revert * Adding textInputStyle and animatedTextStyle to FloatingTextInput component * correction after dev session * adding changes as per new ux * Update edit_profile.tsx * corrections after ux review * ux review * ux review * code clean up * Adding userProfileFields into useMemo * Add enableSaveButton to dependency of submitUser * Added react-native-image-picker * fix picker util * Added action for setDefaultProfileImage * account outline on remove picture * Update edit_profile.tsx * fix image picker * style fix * fix image picker * removed unused types * mmjstool issue with integrity checksum * perform camera permission check for Android * fix to pull latest status * updated ChangeProfilePicture to EditProfilePciture * removed integrity key for mmjstool in package-lock.json * corrections from pr review * bumping react-native-image-picker to v4.7.1 * pod install * update to hooks dependency * fix profile picture component * added event emitter from edit_profile_picture * made hitslop a constant * code clean up * uploadProfilePicture as a remote action * else if profileImage removed * removed check on isBot * update renderProfilePicture dependencies * extractFileInfo with try catch * updated snappoints * Revert "updated snappoints" This reverts commit 6d16d480a168755fc80e5bc80569ad3ba561f73b. * profile picture size * refactored renderProfilePicture into its own component * change to if else * platform select for hasPermissions * unneeded comment removed * else if on prefix in edit profile picture * track has update user info now * moved image_picker under edit_screen and increased actionSheets item height * added preventDoubleTap for imagePicker * multiple uploads * switch the conditions * added alert box as requested by Marina * Revert "added alert box as requested by Marina" This reverts commit 20735c17a87b40995e05eb4318c138c1adcc6c8c. * Apply suggestions from code review Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * removed userInfos constant * added useMemo for certain components on profile_picture * converting account-outline into a constant * added panelItem component * adding return instead of making the function return * eslint fix * update i18n desc Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * hasPictureUrl transferred to file utils * removing excess mediaType prop * add USER_PROFILE_PICTURE_SIZE into constant/profile * relocate hasPictureUrl method * relocate hasPictureUrl * rename ImagePicker to ProfileImagePicker * removing isDestructive property from panelTypes. * update sectionLimit for attachFileFromPhotoGallery * Change animation for showModalOverCurrentContext to a quick alpha on iOS * re-create PickerUtil if intl changes * Combine styles in edit_profile_picture component * Split profile image component into smaller components * useCallback for showFileAttachmentOptions * split comment into multiple lines * edit_profile group refs * remove unnecessary casting * add new line to file.d.ts * remove extra space for utils/index.d.ts * allowMultiSelection for attachFilesFromFiles, default is false * Split edit profile screen into smaller components Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
240 lines
8.2 KiB
TypeScript
240 lines
8.2 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import React, {useCallback, useMemo, useRef} from 'react';
|
|
import {MessageDescriptor, useIntl} from 'react-intl';
|
|
import {Keyboard, StyleSheet, View} from 'react-native';
|
|
|
|
import {FloatingTextInputRef} from '@app/components/floating_text_input_label';
|
|
import {t} from '@app/i18n';
|
|
import {useTheme} from '@context/theme';
|
|
|
|
import DisabledFields from './disabled_fields';
|
|
import EmailField from './email_field';
|
|
import Field from './field';
|
|
|
|
import type UserModel from '@typings/database/models/servers/user';
|
|
import type {FieldConfig, FieldSequence, UserInfo} from '@typings/screens/edit_profile';
|
|
|
|
type Props = {
|
|
canSave: boolean;
|
|
currentUser: UserModel;
|
|
isTablet?: boolean;
|
|
lockedFirstName: boolean;
|
|
lockedLastName: boolean;
|
|
lockedNickname: boolean;
|
|
lockedPosition: boolean;
|
|
onUpdateField: (fieldKey: string, name: string) => void;
|
|
userInfo: UserInfo;
|
|
submitUser: () => void;
|
|
}
|
|
|
|
const includesSsoService = (sso: string) => ['gitlab', 'google', 'office365'].includes(sso);
|
|
const isSAMLOrLDAP = (protocol: string) => ['ldap', 'saml'].includes(protocol);
|
|
|
|
const FIELDS: { [id: string]: MessageDescriptor } = {
|
|
firstName: {
|
|
id: t('user.settings.general.firstName'),
|
|
defaultMessage: 'First Name',
|
|
},
|
|
lastName: {
|
|
id: t('user.settings.general.lastName'),
|
|
defaultMessage: 'Last Name',
|
|
},
|
|
username: {
|
|
id: t('user.settings.general.username'),
|
|
defaultMessage: 'Username',
|
|
},
|
|
nickname: {
|
|
id: t('user.settings.general.nickname'),
|
|
defaultMessage: 'Nickname',
|
|
},
|
|
position: {
|
|
id: t('user.settings.general.position'),
|
|
defaultMessage: 'Position',
|
|
},
|
|
email: {
|
|
id: t('user.settings.general.email'),
|
|
defaultMessage: 'Email',
|
|
},
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
footer: {
|
|
height: 40,
|
|
width: '100%',
|
|
},
|
|
separator: {
|
|
height: 15,
|
|
},
|
|
});
|
|
|
|
const ProfileForm = ({
|
|
canSave, currentUser, isTablet,
|
|
lockedFirstName, lockedLastName, lockedNickname, lockedPosition,
|
|
onUpdateField, userInfo, submitUser,
|
|
}: Props) => {
|
|
const theme = useTheme();
|
|
const {formatMessage} = useIntl();
|
|
const firstNameRef = useRef<FloatingTextInputRef>(null);
|
|
const lastNameRef = useRef<FloatingTextInputRef>(null);
|
|
const usernameRef = useRef<FloatingTextInputRef>(null);
|
|
const emailRef = useRef<FloatingTextInputRef>(null);
|
|
const nicknameRef = useRef<FloatingTextInputRef>(null);
|
|
const positionRef = useRef<FloatingTextInputRef>(null);
|
|
|
|
const userProfileFields: FieldSequence = useMemo(() => {
|
|
const service = currentUser.authService;
|
|
return {
|
|
firstName: {
|
|
ref: firstNameRef,
|
|
isDisabled: (isSAMLOrLDAP(service) && lockedFirstName) || includesSsoService(service),
|
|
},
|
|
lastName: {
|
|
ref: lastNameRef,
|
|
isDisabled: (isSAMLOrLDAP(service) && lockedLastName) || includesSsoService(service),
|
|
},
|
|
username: {
|
|
ref: usernameRef,
|
|
isDisabled: service !== '',
|
|
},
|
|
email: {
|
|
ref: emailRef,
|
|
isDisabled: true,
|
|
},
|
|
nickname: {
|
|
ref: nicknameRef,
|
|
isDisabled: isSAMLOrLDAP(service) && lockedNickname,
|
|
},
|
|
position: {
|
|
ref: positionRef,
|
|
isDisabled: isSAMLOrLDAP(service) && lockedPosition,
|
|
},
|
|
};
|
|
}, [lockedFirstName, lockedLastName, lockedNickname, lockedPosition, currentUser.authService]);
|
|
|
|
const onFocusNextField = useCallback(((fieldKey: string) => {
|
|
const findNextField = () => {
|
|
const fields = Object.keys(userProfileFields);
|
|
const curIndex = fields.indexOf(fieldKey);
|
|
const searchIndex = curIndex + 1;
|
|
|
|
if (curIndex === -1 || searchIndex > fields.length) {
|
|
return undefined;
|
|
}
|
|
|
|
const remainingFields = fields.slice(searchIndex);
|
|
|
|
const nextFieldIndex = remainingFields.findIndex((f: string) => {
|
|
const field = userProfileFields[f];
|
|
return !field.isDisabled;
|
|
});
|
|
|
|
if (nextFieldIndex === -1) {
|
|
return {isLastEnabledField: true, nextField: undefined};
|
|
}
|
|
|
|
const fieldName = remainingFields[nextFieldIndex];
|
|
|
|
return {isLastEnabledField: false, nextField: userProfileFields[fieldName]};
|
|
};
|
|
|
|
const next = findNextField();
|
|
if (next?.isLastEnabledField && canSave) {
|
|
// performs form submission
|
|
Keyboard.dismiss();
|
|
submitUser();
|
|
} else if (next?.nextField) {
|
|
next?.nextField?.ref?.current?.focus();
|
|
} else {
|
|
Keyboard.dismiss();
|
|
}
|
|
}), [canSave, userProfileFields]);
|
|
|
|
const hasDisabledFields = Object.values(userProfileFields).filter((field) => field.isDisabled).length > 0;
|
|
|
|
const fieldConfig: FieldConfig = {
|
|
blurOnSubmit: false,
|
|
enablesReturnKeyAutomatically: true,
|
|
onFocusNextField,
|
|
onTextChange: onUpdateField,
|
|
returnKeyType: 'next',
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{hasDisabledFields && <DisabledFields isTablet={isTablet}/>}
|
|
<Field
|
|
fieldKey='firstName'
|
|
fieldRef={firstNameRef}
|
|
isDisabled={userProfileFields.firstName.isDisabled}
|
|
label={formatMessage(FIELDS.firstName)}
|
|
testID='edit_profile.text_setting.firstName'
|
|
value={userInfo.firstName}
|
|
{...fieldConfig}
|
|
/>
|
|
<View style={styles.separator}/>
|
|
<Field
|
|
fieldKey='lastName'
|
|
fieldRef={lastNameRef}
|
|
isDisabled={userProfileFields.lastName.isDisabled}
|
|
label={formatMessage(FIELDS.lastName)}
|
|
testID='edit_profile.text_setting.lastName'
|
|
value={userInfo.lastName}
|
|
{...fieldConfig}
|
|
/>
|
|
<View style={styles.separator}/>
|
|
<Field
|
|
fieldKey='username'
|
|
fieldRef={usernameRef}
|
|
isDisabled={userProfileFields.username.isDisabled}
|
|
label={formatMessage(FIELDS.username)}
|
|
maxLength={22}
|
|
testID='edit_profile.text_setting.username'
|
|
value={userInfo.username}
|
|
{...fieldConfig}
|
|
/>
|
|
<View style={styles.separator}/>
|
|
{userInfo.email && (
|
|
<EmailField
|
|
authService={currentUser.authService}
|
|
isDisabled={userProfileFields.email.isDisabled}
|
|
email={userInfo.email}
|
|
label={formatMessage(FIELDS.email)}
|
|
fieldRef={emailRef}
|
|
onChange={onUpdateField}
|
|
onFocusNextField={onFocusNextField}
|
|
theme={theme}
|
|
isTablet={Boolean(isTablet)}
|
|
/>
|
|
)}
|
|
<View style={styles.separator}/>
|
|
<Field
|
|
fieldKey='nickname'
|
|
fieldRef={nicknameRef}
|
|
isDisabled={userProfileFields.nickname.isDisabled}
|
|
label={formatMessage(FIELDS.nickname)}
|
|
maxLength={22}
|
|
testID='edit_profile.text_setting.nickname'
|
|
value={userInfo.nickname}
|
|
{...fieldConfig}
|
|
/>
|
|
<View style={styles.separator}/>
|
|
<Field
|
|
fieldKey='position'
|
|
fieldRef={positionRef}
|
|
isDisabled={userProfileFields.position.isDisabled}
|
|
isOptional={true}
|
|
label={formatMessage(FIELDS.position)}
|
|
maxLength={128}
|
|
{...fieldConfig}
|
|
returnKeyType='done'
|
|
testID='edit_profile.text_setting.position'
|
|
value={userInfo.position}
|
|
/>
|
|
<View style={styles.footer}/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ProfileForm;
|