Skip to content

Commit

Permalink
Merge pull request #1767 from Maftalion/matt-update-profile-view
Browse files Browse the repository at this point in the history
[User Settings] Add new Profile view
  • Loading branch information
NikkiWines authored Mar 23, 2021
2 parents 7c2f8b3 + a193424 commit 196f61c
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 25 deletions.
8 changes: 8 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ const CONST = {
TIMEZONE: 'timeZone',
},
DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'},
PRONOUNS: {
THEY_THEM_THEIRS: 'They/them/theirs',
SHE_HER_HERS: 'She/her/hers',
HE_HIM_HIS: 'He/him/his',
ZE_HIR_HIRS: 'Ze/hir/hirs',
SELF_SELECT: 'Self-select',
CALL_ME_BY_MY_NAME: 'Call me by my name',
},
APP_STATE: {
ACTIVE: 'active',
BACKGROUND: 'background',
Expand Down
48 changes: 48 additions & 0 deletions src/components/Checkbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import {View, Pressable, Text} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles/styles';
import Icon from './Icon';
import {Checkmark} from './Icon/Expensicons';

const propTypes = {
// Whether checkbox is checked
isChecked: PropTypes.bool.isRequired,

// A function that is called when the box/label is clicked on
onCheckboxClick: PropTypes.func.isRequired,

// Text that appears next to check box
label: PropTypes.string,
};

const defaultProps = {
label: '',
};

const Checkbox = ({
isChecked,
onCheckboxClick,
label,
}) => (
<View style={styles.flexRow}>
<Pressable onPress={() => onCheckboxClick(!isChecked)}>
<View style={[styles.checkboxContainer, isChecked && styles.checkedContainer]}>
<Icon src={Checkmark} fill="white" height={14} width={14} />
</View>
</Pressable>
{label && (
<Pressable onPress={() => onCheckboxClick(!isChecked)}>
<Text style={[styles.ml2, styles.textP]}>
{label}
</Text>
</Pressable>
)}
</View>
);

Checkbox.defaultProps = defaultProps;
Checkbox.propTypes = propTypes;
Checkbox.displayName = 'Checkbox';

export default Checkbox;
2 changes: 1 addition & 1 deletion src/components/OptionsSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class OptionsSelector extends Component {
onChangeText={this.props.onChangeText}
onKeyPress={this.handleKeyPress}
placeholder={this.props.placeholderText}
placeholderTextColor={themeColors.textSupporting}
placeholderTextColor={themeColors.placeholderText}
/>
</View>
<OptionsList
Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ class ReportActionCompose extends React.Component {
ref={el => this.textInput = el}
textAlignVertical="top"
placeholder="Write something..."
placeholderTextColor={themeColors.textSupporting}
placeholderTextColor={themeColors.placeholderText}
onChangeText={this.updateComment}
onKeyPress={this.triggerSubmitShortcut}
onDragEnter={() => this.setState({isDraggingOver: true})}
Expand Down
280 changes: 268 additions & 12 deletions src/pages/settings/ProfilePage.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,276 @@
import React from 'react';
import React, {Component} from 'react';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import {
View,
TextInput,
Pressable,
} from 'react-native';
import RNPickerSelect from 'react-native-picker-select';
import Str from 'expensify-common/lib/str';
import moment from 'moment-timezone';
import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
import Navigation from '../../libs/Navigation/Navigation';
import ScreenWrapper from '../../components/ScreenWrapper';
import {setPersonalDetails} from '../../libs/actions/PersonalDetails';
import ROUTES from '../../ROUTES';
import ONYXKEYS from '../../ONYXKEYS';
import CONST from '../../CONST';
import Avatar from '../../components/Avatar';
import styles from '../../styles/styles';
import Text from '../../components/Text';
import {DownArrow} from '../../components/Icon/Expensicons';
import Icon from '../../components/Icon';
import Checkbox from '../../components/Checkbox';
import themeColors from '../../styles/themes/default';

const ProfilePage = () => (
<ScreenWrapper>
<HeaderWithCloseButton
title="Profile"
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS)}
onCloseButtonPress={() => Navigation.dismissModal()}
/>
</ScreenWrapper>
);
const propTypes = {
/* Onyx Props */
// The personal details of the person who is logged in
myPersonalDetails: PropTypes.shape({
// Email/Phone login of the current user from their personal details
login: PropTypes.string,

// Display first name of the current user from their personal details
firstName: PropTypes.string,

// Display last name of the current user from their personal details
lastName: PropTypes.string,

// Avatar URL of the current user from their personal details
avatar: PropTypes.string,

// Pronouns of the current user from their personal details
pronouns: PropTypes.string,

// timezone of the current user from their personal details
timezone: PropTypes.shape({

// Value of selected timezone
selected: PropTypes.string,

// Whether timezone is automatically set
automatic: PropTypes.bool,
}),
}),
};

const defaultProps = {
myPersonalDetails: {},
};

const timezones = moment.tz.names()
.map(timezone => ({
value: timezone,
label: timezone,
}));

class ProfilePage extends Component {
constructor(props) {
super(props);

const {
firstName,
lastName,
pronouns,
timezone = {},
} = props.myPersonalDetails;
const pronounsList = Object.values(CONST.PRONOUNS);

let currentUserPronouns = pronouns;
let initialSelfSelectedPronouns = '';

// This handles populating the self-selected pronouns in the form
if (pronouns && !pronounsList.includes(pronouns)) {
currentUserPronouns = CONST.PRONOUNS.SELF_SELECT;
initialSelfSelectedPronouns = pronouns;
}

this.state = {
firstName,
lastName,
pronouns: currentUserPronouns,
selfSelectedPronouns: initialSelfSelectedPronouns,
selectedTimezone: timezone.selected || CONST.DEFAULT_TIME_ZONE.selected,
isAutomaticTimezone: timezone.automatic ?? CONST.DEFAULT_TIME_ZONE.automatic,
};

this.pronounDropdownValues = pronounsList.map(pronoun => ({value: pronoun, label: pronoun}));
this.updatePersonalDetails = this.updatePersonalDetails.bind(this);
this.setAutomaticTimezone = this.setAutomaticTimezone.bind(this);
}

setAutomaticTimezone(isAutomaticTimezone) {
this.setState(({selectedTimezone}) => ({
isAutomaticTimezone,
selectedTimezone: isAutomaticTimezone ? moment.tz.guess() : selectedTimezone,
}));
}

updatePersonalDetails() {
const {
firstName,
lastName,
pronouns,
selfSelectedPronouns,
selectedTimezone,
isAutomaticTimezone,
} = this.state;

setPersonalDetails({
firstName,
lastName,
pronouns: pronouns === CONST.PRONOUNS.SELF_SELECT ? selfSelectedPronouns : pronouns,
timezone: {
automatic: isAutomaticTimezone,
selected: selectedTimezone,
},
});
}

render() {
// Determines if the pronouns/selected pronouns have changed
const arePronounsUnchanged = this.props.myPersonalDetails.pronouns === this.state.pronouns
|| (this.props.myPersonalDetails.pronouns
&& this.props.myPersonalDetails.pronouns === this.state.selfSelectedPronouns);

// Disables button if none of the form values have changed
const isButtonDisabled = (this.props.myPersonalDetails.firstName === this.state.firstName)
&& (this.props.myPersonalDetails.lastName === this.state.lastName)
&& (this.props.myPersonalDetails.timezone.selected === this.state.selectedTimezone)
&& (this.props.myPersonalDetails.timezone.automatic === this.state.isAutomaticTimezone)
&& arePronounsUnchanged;

return (
<ScreenWrapper>
<HeaderWithCloseButton
title="Profile"
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS)}
onCloseButtonPress={Navigation.dismissModal}
/>
<View style={[styles.p5, styles.flex1, styles.overflowScroll]}>
<Avatar
style={[styles.avatarLarge, styles.alignSelfCenter]}
source={this.props.myPersonalDetails.avatar}
/>
<Text style={[styles.mt6, styles.mb6, styles.textP]}>
Tell us about yourself, we would love to get to know you!
</Text>
<View style={[styles.flexRow, styles.mb6]}>
<View style={styles.flex1}>
<Text style={[styles.mb1, styles.formLabel]}>First Name</Text>
<TextInput
style={styles.textInput}
value={this.state.firstName}
onChangeText={firstName => this.setState({firstName})}
placeholder="John"
placeholderTextColor={themeColors.placeholderText}
/>
</View>
<View style={[styles.flex1, styles.ml2]}>
<Text style={[styles.mb1, styles.formLabel]}>Last Name</Text>
<TextInput
style={styles.textInput}
value={this.state.lastName}
onChangeText={lastName => this.setState({lastName})}
placeholder="Doe"
placeholderTextColor={themeColors.placeholderText}
/>
</View>
</View>
<View style={styles.mb6}>
<Text style={[styles.mb1, styles.formLabel]}>Preferred Pronouns</Text>
<View style={styles.mb1}>
<RNPickerSelect
onValueChange={pronouns => this.setState({pronouns, selfSelectedPronouns: ''})}
items={this.pronounDropdownValues}
style={styles.picker}
useNativeAndroidPickerStyle={false}
placeholder={{
value: '',
label: 'Select your pronouns',
}}
value={this.state.pronouns}
Icon={() => <Icon src={DownArrow} />}
/>
</View>
{this.state.pronouns === CONST.PRONOUNS.SELF_SELECT && (
<TextInput
style={styles.textInput}
value={this.state.selfSelectedPronouns}
onChangeText={selfSelectedPronouns => this.setState({selfSelectedPronouns})}
placeholder="Self-select your pronoun"
placeholderTextColor={themeColors.placeholderText}
/>
)}
</View>
<View style={styles.mb6}>
<Text style={[styles.mb1, styles.formLabel]}>
{Str.isSMSLogin(this.props.myPersonalDetails.login)
? 'Phone Number' : 'Email Address'}
</Text>
<TextInput
style={[styles.textInput, styles.disabledTextInput]}
value={Str.isSMSLogin(this.props.myPersonalDetails.login)
? Str.removeSMSDomain(this.props.myPersonalDetails.login)
: this.props.myPersonalDetails.login}
editable={false}
/>
</View>
<View style={styles.mb3}>
<Text style={[styles.mb1, styles.formLabel]}>Timezone</Text>
<RNPickerSelect
onValueChange={selectedTimezone => this.setState({selectedTimezone})}
items={timezones}
style={this.state.isAutomaticTimezone ? {
...styles.picker,
inputIOS: [styles.picker.inputIOS, styles.textInput, styles.disabledTextInput],
inputAndroid: [
styles.picker.inputAndroid, styles.textInput, styles.disabledTextInput,
],
inputWeb: [styles.picker.inputWeb, styles.textInput, styles.disabledTextInput],
} : styles.picker}
useNativeAndroidPickerStyle={false}
value={this.state.selectedTimezone}
Icon={() => <Icon src={DownArrow} />}
disabled={this.state.isAutomaticTimezone}
/>
</View>
<Checkbox
label="Set my timezone automatically"
isChecked={this.state.isAutomaticTimezone}
onCheckboxClick={this.setAutomaticTimezone}
/>
</View>
<View style={[styles.ph5, styles.pb5]}>
<Pressable
disabled={isButtonDisabled}
onPress={this.updatePersonalDetails}
style={({hovered}) => [
styles.button,
styles.buttonSuccess,
styles.w100,
hovered && styles.buttonSuccessHovered,
isButtonDisabled && styles.buttonDisable,
]}
>
<Text style={[styles.buttonText, styles.buttonSuccessText]}>
Save
</Text>
</Pressable>
</View>
</ScreenWrapper>
);
}
}

ProfilePage.propTypes = propTypes;
ProfilePage.defaultProps = defaultProps;
ProfilePage.displayName = 'ProfilePage';

export default ProfilePage;
export default withOnyx({
myPersonalDetails: {
key: ONYXKEYS.MY_PERSONAL_DETAILS,
},
})(ProfilePage);
2 changes: 1 addition & 1 deletion src/pages/signin/LoginForm/LoginFormNarrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class LoginFormNarrow extends React.Component {
onSubmitEditing={this.validateAndSubmitForm}
autoCapitalize="none"
placeholder="Phone or Email"
placeholderTextColor={themeColors.textSupporting}
placeholderTextColor={themeColors.placeholderText}
/>
</View>
<View>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/signin/PasswordForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class PasswordForm extends React.Component {
style={[styles.textInput]}
value={this.state.twoFactorAuthCode}
placeholder="Required when 2FA is enabled"
placeholderTextColor={themeColors.textSupporting}
placeholderTextColor={themeColors.placeholderText}
onChangeText={text => this.setState({twoFactorAuthCode: text})}
onSubmitEditing={this.validateAndSubmitForm}
keyboardType="numeric"
Expand Down
Loading

0 comments on commit 196f61c

Please sign in to comment.