diff --git a/app/components/Profile/EditProfile.container.js b/app/components/Profile/EditProfile.container.js
new file mode 100644
index 0000000..aead19e
--- /dev/null
+++ b/app/components/Profile/EditProfile.container.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+
+import { commonProfileStateToProps } from './Profile.container.js';
+import EditProfile from './EditProfile.js';
+
+const matchStateToProps = (dispatch) => {
+ const commonProps = commonProfileStateToProps(state);
+ const profile = getProfile(state);
+
+ return {
+ ...commonProps,
+ firstName: profile.get('firstName'),
+ lastName: profile.get('lastName'),
+ };
+};
+
+export default connect(matchStateToProps, null)(EditProfile);
diff --git a/app/components/Profile/EditProfile.js b/app/components/Profile/EditProfile.js
new file mode 100644
index 0000000..f352816
--- /dev/null
+++ b/app/components/Profile/EditProfile.js
@@ -0,0 +1,178 @@
+import React, { Component } from 'react';
+import { Text, TextInput, View } from 'react-native';
+import { Avatar } from 'react-native-elements';
+
+import { getEmailAvailability, getNomAvailability } from '../api/profile.js';
+
+import styles from './Profile.styles.js';
+
+const STRINGS = {
+ CANCEL: 'Cancel',
+ 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.',
+ SAVE_PROFILE: 'Save profile',
+};
+
+export default class EditProfile extends Component {
+
+ static get propTypes() {
+ return {
+ addresses: PropTypes.array,
+ avatar: PropTypes.string,
+ cancelEditAction: PropTypes.func.isRequired,
+ email: PropTypes.string,
+ firstName: PropTypes.string,
+ initials: PropTypes.string,
+ lastName: PropTypes.string,
+ nomDeBid: PropTypes.string,
+ phones: PropTypes.array,
+ saveProfileAction: PropTypes.func.isRequired,
+ saveProfileLabel: PropTypes.string,
+ };
+ }
+
+ static get defaultProps() {
+ return {
+ addresses: null,
+ avatar: null,
+ email: null,
+ firstName: null,
+ initials: null,
+ lastName: null,
+ nomDeBid: null,
+ phones: null,
+ saveProfileLabel: STRINGS.SAVE_PROFILE,
+ };
+ }
+
+ constructor() {
+ super(props);
+
+ this.state = {
+ addresses: this.props.addresses,
+ avatar: this.props.avatar,
+ email: this.props.email,
+ firstName: this.props.firstName,
+ lastName: this.props.lastName,
+ invalidEmail: false,
+ invalidNomDeBid: false,
+ nomDeBid: this.props.nomDeBid,
+ password: this.props.password,
+ phones: this.props.phones,
+ };
+
+ this.handleCancel = this.handleCancel.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ _validateEmail() {
+ getEmailAvailability(this.state.email, (result) => this.setState('invalidEmail', !result.available));
+ }
+
+ _validateNomDeBid() {
+ getNomAvailability(this.state.nomDeBid, (result) => this.setState('invalidNomDeBid', !result.available));
+ }
+
+ getProfileFromState() {
+ return {
+ addresses: this.state.addresses,
+ avatar: this.state.avatar,
+ email: this.state.email,
+ firstName: this.state.firstName,
+ lastName: this.state.lastName,
+ nomDeBid: this.state.nomDeBid,
+ password: this.state.password,
+ phones: this.state.phones,
+ };
+ }
+
+ handleCancel() {
+ this.props.cancelEditAction();
+ }
+
+ handleSubmit() {
+ if (!this.isFormComplete()) {
+ console.error('Incomplete form... how did the button become enabled?');
+ alert('Please complete all of the required fields. They have bold labels.');
+ return;
+ }
+
+ this.props.saveProfileAction(this.getProfileFromState());
+ }
+
+ isFormComplete() {
+ return !this.state.invalidEmail && !this.state.invalidNomDeBid &&
+ !!this.state.firstName && !!this.state.lastName && !!this.state.email &&
+ !!this.state.nomDeBid && !!this.state.phones.length && !!this.state.password;
+ }
+
+ render() {
+ const { avatar, firstName, lastName } = this.state;
+ const avatarTitle = !avatar && firstName && lastName
+ ? `${firstName.substring(0,1)}${lastName.substring(0,1)}`
+ : null;
+
+ return (
+
+
+ {avatar !== null ? (
+
+ ) : (
+
+ )}
+
+
+ this.setState({ firstName: text })}
+ placeholder="first name"
+ style={[styles.textInput, styles.requiredInput]}
+ value={this.state.firstName}
+ />
+ this.setState({ lastName: text })}
+ placeholder="last name"
+ style={[styles.textInput, styles.requiredInput]}
+ value={this.state.lastName}
+ />
+
+
+ this.setState({ email: text })}
+ onEndEditing={(text) => this._validateEmail(text)}
+ placeholder="email address"
+ style={[styles.textInput, styles.requiredInput]}
+ value={this.state.email}
+ />
+
+
+ {STRINGS.NOM_EXPLANATION}
+ this.setState({ nomDeBid: text })}
+ onEndEditing={(text) => this._validateEmail(text)}
+ placeholder="nom de bid"
+ style={[styles.textInput, styles.requiredInput]}
+ value={this.state.nomDeBid}
+ />
+
+
+ Numbers
+ {phones.length > 0 && (
+ // LIST PHONES
+ )}
+
+
+ Addresses
+ {addresses.length > 0 && (
+ // LIST ADDRESSES
+ )}
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/app/components/Profile/Profile.container.js b/app/components/Profile/Profile.container.js
new file mode 100644
index 0000000..1a08697
--- /dev/null
+++ b/app/components/Profile/Profile.container.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux';
+
+import { getProfile } from '../selectors/profile.js';
+
+import Profile from './Profile.js';
+
+export const commonProfileStateToProps = (state) => {
+ const profile = getProfile(state);
+
+ return {
+ addresses: profile.get('addresses').toArray(),
+ avatar: profile.get('avatar'),
+ email: profile.get('email'),
+ initials: profile.get('initials'),
+ nomDeBid: profile.get('nomDeBid'),
+ phones: profile.get('phones'),
+ };
+};
+
+export default connect(commonProfileStateToProps, null)(Profile);
diff --git a/app/components/Profile/Profile.js b/app/components/Profile/Profile.js
new file mode 100644
index 0000000..d97dcc2
--- /dev/null
+++ b/app/components/Profile/Profile.js
@@ -0,0 +1,50 @@
+import React, { useState } from 'react';
+import { View } from 'react-native';
+
+import EditProfile from './EditProfile.container.js';
+import ViewProfile from './ViewProfile.container.js';
+
+export default function Profile({
+ cancelEditAction,
+ isInEditMode,
+ saveProfileAction,
+ saveProfileLabel,
+}) {
+ const [editMode, setEditMode] = useState(isInEditMode);
+
+ const _cancelEditAction = () => {
+ setEditMode(false);
+ cancelEditAction();
+ };
+
+ const _saveProfileAction = (profile) => {
+ setEditMode(false);
+ saveProfileAction(profile);
+ }
+
+ return (
+
+ {editMode ? (
+
+ ) : (
+ setEditMode(true)} />
+ )}
+
+ );
+}
+
+Profile.propTypes = {
+ cancelEditAction: PropTypes.func.isRequired,
+ isInEditMode: PropTypes.bool,
+ saveProfileAction: PropTypes.func.isRequired,
+ saveProfileLabel: PropTypes.string,
+};
+
+Profile.defaultProps = {
+ isInEditMode: false,
+ saveProfileLabel: null,
+};
diff --git a/app/components/Profile/Profile.styles.js b/app/components/Profile/Profile.styles.js
new file mode 100644
index 0000000..e69de29
diff --git a/app/components/Profile/ViewProfile.container.js b/app/components/Profile/ViewProfile.container.js
new file mode 100644
index 0000000..5216149
--- /dev/null
+++ b/app/components/Profile/ViewProfile.container.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+
+import { commonProfileStateToProps } from './Profile.container.js';
+import ViewProfile from './ViewProfile.js';
+
+const matchStateToProps = (dispatch) => {
+ const commonProps = commonProfileStateToProps(state);
+ const profile = getProfile(state);
+
+ return {
+ ...commonProps,
+ fullName: profile.get('fullName'),
+ generatedNomDeBid: propfile.get('generatedNomDeBid'),
+ };
+};
+
+export default connect(matchStateToProps, null)(ViewProfile);
diff --git a/app/components/Profile/ViewProfile.js b/app/components/Profile/ViewProfile.js
new file mode 100644
index 0000000..f4a9966
--- /dev/null
+++ b/app/components/Profile/ViewProfile.js
@@ -0,0 +1,102 @@
+import React, { useState } from 'react';
+import { Text, TextInput, View } from 'react-native';
+import { Avatar } from 'react-native-elements';
+
+import { getEmailAvailability, getNomAvailability } from '../api/profile.js';
+
+import styles from './Profile.styles.js';
+
+const STRINGS = {
+ EDIT: 'Edit profile',
+};
+
+export default function ViewProfile({
+ addresses,
+ avatar,
+ editProfileAction,
+ email,
+ fullName,
+ generatedNomDeBid,
+ initials,
+ nomDeBid,
+ phones,
+}) {
+ const addressesCount = addresses.length;
+ const phonesCount = phones.length;
+
+ const [isEditingNom, setEditNom] = useState(false);
+
+ return (
+
+
+ {avatar !== null ? (
+
+ ) : (
+
+ )}
+
+
+ {fullName}
+
+ {isEditingNom ? (
+ {nomDeBid}
+ {generatedNomDeBid && (
+
+
+
+ email
+ {email}
+
+
+ numbers
+ {`${phonesCount} saved`}
+
+
+ addresses
+ {`${addressesCount} saved`}
+
+ {editProfileAction !== null && (
+
+
+
+ )}
+
+ );
+}
+
+ViewProfile.propTypes = {
+ addresses: PropTypes.array,
+ avatar: PropTypes.string,
+ editProfileAction: PropTypes.func,
+ email: PropTypes.string,
+ fullName: PropTypes.string,
+ generatedNomDeBid: PropTypes.bool,
+ initials: PropTypes.string,
+ nomDeBid: PropTypes.string,
+ phones: PropTypes.array,
+};
+
+ViewProfile.defaultProps = {
+ addresses: [],
+ avatar: null,
+ editProfileAction: null,
+ email: null,
+ fullName: null,
+ generatedNomDeBid: false,
+ initials: null,
+ nomDeBid: null,
+ phones: [],
+};
+
+ return (
+ );
+ }
+}
diff --git a/app/domain/Profile.js b/app/domain/Profile.js
index 11b2069..f5629f4 100644
--- a/app/domain/Profile.js
+++ b/app/domain/Profile.js
@@ -28,6 +28,17 @@ export default class Profile extends Record({
return `${this.firstName} ${this.lastName}`;
}
+ get initials() {
+ const firstInitial = this.firstName ? this.firstName.substring(0,1) : null;
+ const lastInitial = this.firstName ? this.firstName.substring(0,1) : null;
+
+ if (!firstInitial && !lastInitial) {
+ return null;
+ }
+
+ return `${firstInitial || ''}${lastInitial || ''}`;
+ }
+
get isRegisteredAccount() {
return (
this.hasLinkedApple ||
diff --git a/app/screens/Profile.container.js b/app/screens/Profile.container.js
index 1170cf8..be75d3c 100644
--- a/app/screens/Profile.container.js
+++ b/app/screens/Profile.container.js
@@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import { fetchProfile, updateProfile } from '../actions/profile.js';
-import { getNomDeBid, getProfile, isAllowedToBid } from '../selectors/profile.js';
+import { getProfile, isAllowedToBid } from '../selectors/profile.js';
import Profile from './Profile.js';
@@ -13,13 +13,8 @@ const matchStateToProps = (state) => {
hasLinkedFacebook: profile.get('hasLinkedFacebook'),
hasLinkedGoogle: profile.get('hasLinkedGoogle'),
hasLocalAccount: profile.get('hasLocalAccount'),
- hasRegisteredAcccount: profile.get('hasRegisteredAcccount'),
- id: profile.get('id'),
isAllowedToBid: isAllowedToBid(state),
isVerified: profile.get('isVerified'),
- lastName: profile.get('lastName'),
- nomDeBid: getNomDeBid(state),
- organizationIdentifier: profile.get('organizationIdentifier'),
paymentToken: profile.get('paymentToken'),
};
};
diff --git a/app/screens/Profile.js b/app/screens/Profile.js
index cd935c7..706ddd2 100644
--- a/app/screens/Profile.js
+++ b/app/screens/Profile.js
@@ -1,26 +1,87 @@
import React, { Component } from 'react';
-import { StyleSheet, Text, View } from 'react-native';
+import { Text, View } from 'react-native';
+
+import ProfileUtility from '../components/Profile/Profile.container.js';
+
+import styles from './Profile.styles.js';
export default class Profile extends Component {
+
+ static get propTypes() {
+ return {
+ hasLinkedApple: PropTypes.bool,
+ hasLinkedFacebook: PropTypes.bool,
+ hasLinkedGoogle: PropTypes.bool,
+ hasLocalAccount: PropTypes.bool,
+ isAllowedToBid: PropTypes.bool,
+ isVerified: PropTypes.bool,
+ paymentToken: PropTypes.string,
+ updateProfile: PropTypes.func.isRequired,
+ };
+ }
+
+ static get defaultProps() {
+ return {
+ hasLinkedApple: false,
+ hasLinkedFacebook: false,
+ hasLinkedGoogle: false,
+ hasLocalAccount: false,
+ isAllowedToBid: false,
+ isVerified: false,
+ paymentToken: null,
+ };
+ }
+
render() {
+ const {
+ hasLinkedApple,
+ hasLinkedFacebook,
+ hasLinkedGoogle,
+ hasLocalAccount,
+ isAllowedToBid,
+ isVerified,
+ paymentToken,
+ updateProfile,
+ } = this.props;
+
return (
- Profile
+ {!isVerified && (
+
+
+ {`Your acount has not been verified, please check your email.`}
+
+
+ )}
+
+ false}
+ saveProfileAction={updateProfile}
+ saveProfileLabel="Update profile"
+ />
+
+ {!isAllowedToBid ? (
+ /* ADD PAYMENT METHOD */
+ ) : (
+ /* SHOW/EDIT PAYMENT METHOD */
+ )}
+
+ {!hasLocalAccount && (
+ /* CREATE LOCAL ACCOUNT PASSWORD CTA */
+ )}
+
+ {hasLinkedApple && (
+ /* APPLE LINK/UNLINK */
+ )}
+
+ {hasLinkedFacebook && (
+ /* FACEBOOK LINK/UNLINK */
+ )}
+
+ {hasLinkedGoogle && (
+ /* GOOGLE LINK/UNLINK */
+ )}
);
}
}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: '#F5FCFF',
- },
- title: {
- fontSize: 20,
- textAlign: 'center',
- margin: 10,
- },
-});
diff --git a/app/screens/Profile.styles.js b/app/screens/Profile.styles.js
new file mode 100644
index 0000000..cebb486
--- /dev/null
+++ b/app/screens/Profile.styles.js
@@ -0,0 +1,15 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: '#F5FCFF',
+ },
+ title: {
+ fontSize: 20,
+ textAlign: 'center',
+ margin: 10,
+ },
+});
diff --git a/app/screens/Register.js b/app/screens/Register.js
index b5f66f8..5ab2f7c 100644
--- a/app/screens/Register.js
+++ b/app/screens/Register.js
@@ -1,132 +1,32 @@
-import React, { Component } from 'react';
+import React from 'react';
import { Text, View } from 'react-native';
+import EditProfile from '../components/Profile/EditProfile.container.js';
+
import styles from './Register.styles.js';
-const STRINGS = {
- 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.',
- SUBMIT_REGISTRATION: 'Register',
-};
+export default function Register({ doRegistration, navigation }) {
-export default class Register extends Component {
- static get propTypes() {
- return {
- checkEmail: PropTypes.func.isRequired,
- checkNomDeBid: PropTypes.func.isRequired,
- doRegistration: PropTypes.func.isRequired,
- // invalidEmail: PropTypes.bool.isRequired,
- // invalidNomDeBid: PropTypes.bool.isRequired,
- };
- }
-
- constructor() {
- super(props);
-
- this.state = {
- addresses: [],
- avatar: null,
- email: null,
- firstName: null,
- lastName: null,
- nomDeBid: null,
- invalidEmail: false,
- invalidNomDeBid: false,
- password: null,
- phones: [],
- };
-
- this._doRegistration = this._doRegistration.bind(this);
- }
-
- _doRegistration() {
- if (!this.isFormComplete()) {
- console.error('Incomplete form... how did the button become enabled?');
- alert('Please complete all of the required fields. They have bold labels.');
+ const _doRegistration = (profile) => {
+ if (!profile) {
return;
}
- this.props.doRegistration(this.getUserRegistration());
- }
+ doRegistration(profile);
+ };
- _validateEmail() {
- this.props.checkEmail(this.state.email, (valid) => this.setState('invalidEmail', valid));
- }
-
- _validateNomDeBid() {
- this.props.checkNomDeBid(this.state.nomDeBid, (valid) =>
- this.setState('invalidNomDeBid', valid),
- );
- }
-
- getUserRegistration() {
- return {
- addresses: this.state.addresses,
- avatar: this.state.avatar,
- email: this.state.email,
- firstName: this.state.firstName,
- lastName: this.state.lastName,
- nomDeBid: this.state.nomDeBid,
- password: this.state.password,
- phones: this.state.phones,
- };
- }
-
- isFormComplete() {
- return (
- !this.state.invalidEmail &&
- !this.state.invalidNomDeBid &&
- !!this.state.firstName &&
- !!this.state.lastName &&
- !!this.state.email &&
- !!this.state.nomDeBid &&
- !!this.state.phones.length &&
- !!this.state.password
- );
- }
-
- render() {
return (
- Register
-
- this.setState({ firstName: text })}
- placeholder="first name"
- style={styles.textInput}
- value={this.state.firstName}
+ Register
+ navigation.goBack()}
+ saveProfileAction={doRegistration}
+ saveProfileLabel="Register"
/>
- this.setState({ lastName: text })}
- placeholder="last name"
- style={styles.textInput}
- value={this.state.lastName}
- />
-
-
- _updateState('username', text)}
- onEndEditing={(text) => this._validateEmail(text)}
- placeholder="email address"
- style={styles.textInput}
- value={this.state.email}
- />
-
-
- {STRINGS.NOM_EXPLANATION}
- _updateState('username', text)}
- onEndEditing={(text) => this._validateEmail(text)}
- placeholder="email address"
- style={styles.textInput}
- value={this.state.email}
- />
-
-
-
-
);
- }
}
+
+Register.propTypes = {
+ doRegistration: PropTypes.func.isRequired,
+};
diff --git a/app/screens/Register.styles.js b/app/screens/Register.styles.js
index a430557..38e950b 100644
--- a/app/screens/Register.styles.js
+++ b/app/screens/Register.styles.js
@@ -1,12 +1,9 @@
import { StyleSheet } from 'react-native';
-export default (styles = StyleSheet.create({
+export default StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
},
- title: {},
- localLogin: {},
- services: {},
- register: {},
-}));
+ heading: {},
+});
diff --git a/app/screens/SignInOrRegister.js b/app/screens/SignInOrRegister.js
index 6cc3315..666dfa7 100644
--- a/app/screens/SignInOrRegister.js
+++ b/app/screens/SignInOrRegister.js
@@ -1,37 +1,24 @@
import React, { Component } from 'react';
import { Text, View } from 'react-native';
-import { Button } from 'react-native-elements';
import FacebookLogin from '../components/Login/FacebookLogin.container.js';
import LocalLogin from '../components/Login/LocalLogin.container.js';
import styles from './SignInOrRegister.styles.js';
-export default class SignInOrRegister extends Component {
- constructor() {
- super(props);
-
- this._doRegistration = this._doRegistration.bind(this);
- }
-
- _doRegistration() {
- this.props.navigation.navigate('Register');
- }
-
- render() {
- return (
-
- Sign In or Register
-
-
-
-
-
-
-
-
-
+export default function SignInOrRegister({ navigation }) {
+ return (
+
+ Sign In or Register
+
+
- );
- }
+
+
+
+
+
+
+ );
}