- Breaking up the profile
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { setNomDeBid } from '../../../actions/profile.js';
|
||||
import { getNomDeBid, isGeneratedNomDeBid } from '../../../selectors/profile.js';
|
||||
|
||||
import EditNomDeBid from './EditNomDeBid.js';
|
||||
|
||||
const matchStateToProps = (state) => ({
|
||||
isGeneratedNomDeBid: isGeneratedNomDeBid(state),
|
||||
nomDeBid: getNomDeBid(state),
|
||||
});
|
||||
|
||||
const matchDispatchToProps = (dispatch) => ({
|
||||
updateNomDeBid: (nomDeBid) => dispatch(setNomDeBid(nomDeBid)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
matchStateToProps,
|
||||
matchDispatchToProps,
|
||||
)(EditNomDeBid);
|
||||
79
app/components/Profile/ProfileInputs/EditNomDeBid.js
Normal file
79
app/components/Profile/ProfileInputs/EditNomDeBid.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Text, TextInput, View } from 'react-native';
|
||||
import { Avatar } from 'react-native-elements';
|
||||
|
||||
import { getNomAvailaibility } from '../../../api/profile.js';
|
||||
import styles from '../Profile.styles.js';
|
||||
|
||||
const STRINGS = {
|
||||
ABOUT_GENERATED_NOM:
|
||||
'You currently have a generated Nom De Bid - the alias other bidders will know you as - why not personalize it?',
|
||||
CHECK_AVAILABILITY: 'check availability',
|
||||
NOM_EXPLANATION:
|
||||
"Selecting a nom de bid allows you to bid anonymously - or not. By default, we'll use your first initial and last name.",
|
||||
ONLY_SET_ONCE: 'This can only be set once!',
|
||||
PLACEHOLDER: 'nom de bid',
|
||||
SUBMIT_NOM: 'Set Nom De Bid',
|
||||
};
|
||||
|
||||
export default function EditNomDeBid({
|
||||
isGeneratedNomDeBid,
|
||||
isStandalone,
|
||||
nomDeBid,
|
||||
updateNomDeBid,
|
||||
}) {
|
||||
const [newNom, setNomDeBid] = useState(isGeneratedNomDeBid || !nomDeBid ? '' : nomDeBid);
|
||||
const [isNomValid, setValidNom] = useState(null);
|
||||
|
||||
const _handleEndEditing = (updateOnValid = false) => () => {
|
||||
getNomAvailaibility(newNom).then((result) => {
|
||||
setValidNom(result.available);
|
||||
|
||||
if (updateOnValid && result.available) {
|
||||
updateNomDeBid(newNom);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const explanationString = isGeneratedNomDeBid
|
||||
? `${STRINGS.ABOUT_GENERATED_NOM} ${STRINGS.ONLY_SET_ONCE}`
|
||||
: `${STRINGS.NOM_EXPLANATION} ${STRINGS.ONLY_SET_ONCE}`;
|
||||
|
||||
return (
|
||||
<View style={styles.profileFormWrap}>
|
||||
<Text style={styles.hintText}>{explanationString}</Text>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
onChangeText={(text) => setNomDeBid(text)}
|
||||
onEndEditing={!isStandalone && _handleEndEditing(false)}
|
||||
placeholder={STRINGS.PLACEHOLDER}
|
||||
style={[styles.textInput, styles.requiredInput]}
|
||||
value={newNom}
|
||||
/>
|
||||
{isNomValid === false && (
|
||||
<Text style={{color:'red'}}>Nom De Bid is taken!</Text>
|
||||
)}
|
||||
{isNomValid === true && (
|
||||
<Text style={{color:'green'}}>Nom De Bid is available!</Text>
|
||||
)}
|
||||
<Button
|
||||
title={isStandalone ? STRINGS.CHECK_AVAILABILITY : STRINGS.SUBMIT_NOM}
|
||||
onPress={_handleEndEditing(true)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
EditNomDeBid.propTypes = {
|
||||
isGeneratedNomDeBid: PropTypes.bool,
|
||||
isStandalone: PropTypes.bool,
|
||||
nomDeBid: PropTypes.string,
|
||||
updateNomDeBid: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
EditNomDeBid.defaultProps = {
|
||||
isGeneratedNomDeBid: false,
|
||||
isStandalone: false,
|
||||
nomDeBid: null,
|
||||
};
|
||||
86
app/components/Profile/ProfileInputs/EmailInput.js
Normal file
86
app/components/Profile/ProfileInputs/EmailInput.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Text, TextInput, View } from 'react-native';
|
||||
|
||||
import { getEmailAvailability } from '../../../api/profile.js';
|
||||
|
||||
import styles from '../Profile.styles.js';
|
||||
|
||||
const STRINGS = {
|
||||
CONTINUE: 'continue',
|
||||
PLACEHOLDER: 'email address',
|
||||
REGISTERED: `You've already registered!`,
|
||||
RESET_PASSWORD: 'reset password',
|
||||
};
|
||||
|
||||
export default function EmailInput({ email, handleValidEmailEntry, isRequired, showContinueButton }) {
|
||||
const [emailValue, setEmail] = useState(email);
|
||||
const [isEmailAvailable, setEmailAvailable] = useState(null);
|
||||
|
||||
const _handleContinueButtonPress = () => {
|
||||
|
||||
};
|
||||
|
||||
const _handleEndEditing = () => {
|
||||
if (!emailValue.match(/.+\@.+\..+/)) {
|
||||
Alert.alert(
|
||||
'Invalid Email',
|
||||
`Hmmm... You entered '${emailValue}' and something doesn't look quite right...`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
getEmailAvailability(emailValue)
|
||||
.then((result) => {
|
||||
const { available } = result;
|
||||
|
||||
console.log(`EmailInput._validateEmail => getEmailAvailability(${emailValue}):`, result);
|
||||
|
||||
if (available) {
|
||||
handleValidEmailEntry(emailValue);
|
||||
}
|
||||
|
||||
setEmailAvailable(available);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.sectionWrap, styles.password, isRequired && styles.requiredWrap]}>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
onChangeText={setEmail}
|
||||
onEndEditing={showContinueButton ? null : _handleEndEditing}
|
||||
placeholder={STRINGS.PLACEHOLDER}
|
||||
style={[styles.textInput, isRequired && styles.requiredInput]}
|
||||
value={emailValue || ''}
|
||||
/>
|
||||
{isEmailAvailable === false && (
|
||||
<View style={styles.emailTaken}>
|
||||
<Text style={{color:'red'}}>{STRINGS.REGISTERED}</Text>
|
||||
<Button title={STRINGS.RESET_PASSWORD} onPress={() => {}} />
|
||||
</View>
|
||||
)}
|
||||
{showContinueButton && !isEmailAvailable && (
|
||||
<Button
|
||||
onPress={_handleEndEditing}
|
||||
title={STRINGS.CONTINUE}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
EmailInput.propTypes = {
|
||||
email: PropTypes.string,
|
||||
handleValidEmailEntry: PropTypes.func.isRequired,
|
||||
isRequired: PropTypes.bool,
|
||||
showContinueButton: PropTypes.bool,
|
||||
};
|
||||
|
||||
EmailInput.defaultProps = {
|
||||
email: null,
|
||||
isRequired: false,
|
||||
showContinueButton: false,
|
||||
}
|
||||
53
app/components/Profile/ProfileInputs/PasswordInput.js
Normal file
53
app/components/Profile/ProfileInputs/PasswordInput.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, TextInput, View } from 'react-native';
|
||||
|
||||
import styles from '../Profile.styles.js';
|
||||
|
||||
const STRINGS = {
|
||||
HINT: `Password must be at least 8 characters and contain a minimum of one lowercase and one uppercase letter, a number, and a symbol. Spaces are allowed. Be memorable and secure!`,
|
||||
MATCH: `That's a match!`,
|
||||
NO_MATCH: `Well that's not a match...`,
|
||||
PLACEHOLDER_PASSWORD: 'password',
|
||||
PLACEHOLDER_VERIFY: 're-enter password',
|
||||
};
|
||||
|
||||
export default function PasswordInput({ handleValidPasswordEntry }) {
|
||||
const [password, setPassword] = useState(null);
|
||||
const [doPasswordsMatch, setPasswordMatch] = useState(null);
|
||||
|
||||
const _doesVerificationPasswordMatch = (text) => {
|
||||
if (!password || password.length < text.length) {
|
||||
setPasswordMatch(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = password === text;
|
||||
handleValidPasswordEntry(result && password);
|
||||
setPasswordMatch(result);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.sectionWrap, styles.password, styles.requiredWrap]}>
|
||||
<Text style={styles.hintText}>{STRINGS.HINT}</Text>
|
||||
<TextInput
|
||||
onChangeText={setPassword}
|
||||
placeholder={STRINGS.PLACEHOLDER_PASSWORD}
|
||||
secureTextEntry
|
||||
style={[styles.textInput, styles.requiredInput]}
|
||||
/>
|
||||
<TextInput
|
||||
onChangeText={_doesVerificationPasswordMatch}
|
||||
placeholder={STRINGS.PLACEHOLDER_VERIFY}
|
||||
secureTextEntry
|
||||
style={[styles.textInput, styles.requiredInput]}
|
||||
/>
|
||||
{doPasswordsMatch === true && <Text style={{ color: 'green' }}>{STRINGS.MATCH}</Text>}
|
||||
{doPasswordsMatch === false && <Text style={{ color: 'red' }}>{STRINGS.NO_MATCH}</Text>}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
PasswordInput.propTypes = {
|
||||
handleValidPasswordEntry: PropTypes.func.isRequired,
|
||||
};
|
||||
122
app/components/Profile/ProfileInputs/PhoneListInput.js
Normal file
122
app/components/Profile/ProfileInputs/PhoneListInput.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { List } from 'immutable';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Button, Picker, Text, TextInput, View } from 'react-native';
|
||||
|
||||
import { PHONE_TYPE_DEFAULT, PHONE_TYPES } from '../../../constants/constants.js';
|
||||
|
||||
import PhoneListItem from './PhoneListItem.js';
|
||||
|
||||
import styles from '../Profile.styles.js';
|
||||
|
||||
const defaultState = {
|
||||
editingIndex: null,
|
||||
isEditing: false,
|
||||
newPhone: null,
|
||||
newPhoneType: PHONE_TYPE_DEFAULT,
|
||||
};
|
||||
|
||||
export default class PhoneListInput extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
handleAdd: PropTypes.func.isRequired,
|
||||
handleDelete: PropTypes.func.isRequired,
|
||||
handleEdit: PropTypes.func,
|
||||
phones: PropTypes.instanceOf(List).isRequired,
|
||||
};
|
||||
};
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
handleEdit: null,
|
||||
};
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { ...defaultState };
|
||||
|
||||
this.handleAdd = this.handleAdd.bind(this);
|
||||
this.handleEdit = this.handleEdit.bind(this);
|
||||
this.handleEditCancel = this.handleEditCancel.bind(this);
|
||||
}
|
||||
|
||||
handleAdd() {
|
||||
const { phones } = this.props;
|
||||
const { newPhone, newPhoneType } = this.state;
|
||||
this.props.handleAdd(phones.push({ number: newPhone, label: newPhoneType }));
|
||||
this.setState(defaultState);
|
||||
}
|
||||
|
||||
handleEdit(index) {
|
||||
const { phones } = this.props;
|
||||
const { newPhone, newPhoneType } = this.state;
|
||||
this.props.handleEdit(phones.set(index, { number: newPhone, label: newPhoneType }));
|
||||
this.setState(defaultState);
|
||||
}
|
||||
|
||||
handleEditStart(index) {
|
||||
const toBeEdited = this.props.phones.get(index);
|
||||
this.setState({
|
||||
editingIndex: index,
|
||||
isEditing: true,
|
||||
newPhone: toBeEdited.get('number'),
|
||||
newPhoneType: toBeEdited.get('label'),
|
||||
});
|
||||
}
|
||||
|
||||
handleEditCancel(index) {
|
||||
this.setState(defaultState);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { phones } = this.props;
|
||||
const { isEditing, newPhone, newPhoneType } = this.state;
|
||||
const numbersTitle = 'Numbers';
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text style={[styles.groupHeading, styles.requiredLabel]}>{numbersTitle}</Text>
|
||||
{phones !== null && phones.size > 0 && (
|
||||
<View style={styles.phoneList}>
|
||||
{phones.map((phone, index) =>
|
||||
<PhoneListItem
|
||||
hideDelete={phones.size < 2}
|
||||
index={index}
|
||||
label={phone.label}
|
||||
number={phone.number}
|
||||
handleDelete={this.props.handleDelete}
|
||||
handleEdit={this.props.handleEdit ? this.handleEdit : null}
|
||||
handleEditStart={this.handleEditStart}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
<TextInput
|
||||
onChangeText={(text) => this.setState({ newPhone: text })}
|
||||
placeholder="phone number"
|
||||
style={[styles.textInput, styles.requiredInput]}
|
||||
value={this.state.newPhone}
|
||||
/>
|
||||
<Picker
|
||||
onValueChange={(type, index) => this.setState({ newPhoneType: type })}
|
||||
selectedValue={this.state.newPhoneType}
|
||||
>
|
||||
{PHONE_TYPES.map((type) => <Picker.Item key={type.value} {...type} />)}
|
||||
</Picker>
|
||||
<Button
|
||||
disabled={!this.state.newPhone && !this.state.newPhoneType}
|
||||
onPress={isEditing ? () => this.handleEdit(index) : this.handleAdd}
|
||||
title={isEditing ? 'update' : 'add number'}
|
||||
/>
|
||||
{isEditing && (
|
||||
<Button
|
||||
onPress={this.handleEditCancel}
|
||||
title="cancel"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
76
app/components/Profile/ProfileInputs/PhoneListItem.js
Normal file
76
app/components/Profile/ProfileInputs/PhoneListItem.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button } from 'react-native-elements';
|
||||
import { Alert, Text, View } from 'react-native';
|
||||
|
||||
import styles from '../Profile.styles.js';
|
||||
|
||||
export default function PhoneListItem({
|
||||
hideDelete,
|
||||
index,
|
||||
label,
|
||||
number,
|
||||
handleDelete,
|
||||
handleEdit,
|
||||
handleEditStart,
|
||||
}) {
|
||||
|
||||
const _onLongPressDelete = () => {
|
||||
handleDelete(index);
|
||||
};
|
||||
|
||||
const _onPressDelete = (isLongPress = false) => () => {
|
||||
if (isLongPress) {
|
||||
handleDelete(index);
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Delete this number?',
|
||||
'Are you sure you want to delete this number?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ text: 'OK', onPress: () => handleDelete(index) },
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.listItem}>
|
||||
<View style={styles.listValue}>
|
||||
<Text style={styles.value}>{number}</Text>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
</View>
|
||||
<View style={styles.listActions}>
|
||||
{handleEdit !== null && <Button accessibilityLabel={`Edit`} onPress={() => handleEditStart()} />}
|
||||
{!hideDelete && (
|
||||
<Button
|
||||
accessibilityLabel={`Delete`}
|
||||
onLongPress={_onPressDelete(true)}
|
||||
onPress={_onPressDelete()}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
PhoneListItem.propTypes = {
|
||||
hideDelete: PropTypes.bool,
|
||||
index: PropTypes.number.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
number: PropTypes.string.isRequired,
|
||||
handleDelete: PropTypes.func.isRequired,
|
||||
handleEdit: PropTypes.func,
|
||||
handleEditStart: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
PhoneListItem.defaultProps = {
|
||||
hideDelete: false,
|
||||
handleEdit: null,
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user