Merge branch 'feature/PROFILE_-_Registration' of honey.fitz.guru:eventment-app into feature/PROFILE_-_Registration

# Conflicts:
#	app/components/AppHeader/HeaderContentLeft.js
#	app/components/AppHeader/HeaderTitle/EventTitle/EventTitle.js
This commit is contained in:
2019-08-08 20:58:01 -04:00
44 changed files with 432 additions and 242 deletions

View File

@@ -5,6 +5,7 @@ import {
getNomAvailaibility, getNomAvailaibility,
loginUser, loginUser,
registerNewUser, registerNewUser,
setNomDeBid as setNomDeBidApi,
} from '../api/profile.js'; } from '../api/profile.js';
import { import {
@@ -15,11 +16,16 @@ import {
PROFILE_NOM_AVAILABLE, PROFILE_NOM_AVAILABLE,
REGISTRATION_FAILURE, REGISTRATION_FAILURE,
REGISTRATION_SUCCESS, REGISTRATION_SUCCESS,
SET_NOM_FAILURE,
SET_NOM_SUCCESS,
UNSET_AUTH, UNSET_AUTH,
UNSET_PROFILE, UNSET_PROFILE,
UPDATE_PROFILE, UPDATE_PROFILE,
} from '../constants/actionTypes.js'; } from '../constants/actionTypes.js';
import { getAuthToken } from '../selectors/auth.js';
import { getUserId } from '../selectors/profile.js';
const isValidEmail = (payload) => ({ const isValidEmail = (payload) => ({
type: PROFILE_EMAIL_AVAILABLE, type: PROFILE_EMAIL_AVAILABLE,
payload, payload,
@@ -44,6 +50,16 @@ const logoutUser = () => ({
type: DO_LOGOUT, type: DO_LOGOUT,
}); });
export const setNomFailure = ({ info }) => ({
type: SET_NOM_FAILURE,
payload: info,
});
export const setNomSuccess = ({ nomDeBid }) => ({
type: SET_NOM_SUCCESS,
payload: nomDeBid,
});
const registrationFailure = (payload) => ({ const registrationFailure = (payload) => ({
type: REGISTRATION_FAILURE, type: REGISTRATION_FAILURE,
payload, payload,
@@ -71,6 +87,15 @@ export const checkEmailAvailability = (email) => (dispatch) => {};
export const checkNomAvailability = (nomDeBid) => (dispatch) => {}; export const checkNomAvailability = (nomDeBid) => (dispatch) => {};
export const setNomDeBid = (nomDeBid) => (dispatch, getState) => {
const id = getUserId(getState());
const auth = getAuthToken(getState());
setNomDeBidApi({ id, nomDeBid }, auth)
.then((result) => dispatch(setNomSuccess(result)))
.catch((err) => dispatch(setNomFailure(err)));
};
export const login = (username, password) => (dispatch) => { export const login = (username, password) => (dispatch) => {
dispatch(blockUI()); dispatch(blockUI());
loginUser(username, password) loginUser(username, password)

View File

@@ -17,3 +17,10 @@ export const registerNewUser = (user) =>
path: API_ENDPOINTS.USER_SIGNUP, path: API_ENDPOINTS.USER_SIGNUP,
body: { user }, body: { user },
}); });
export const setNomDeBid = (id, auth) => (nomDeBid) =>
requestPost({
path: `${API_ENDPOINTS.SET_NOM}/${id}`,
body: { nomDeBid },
requestOptions: { Authorization: auth ? `Bearer ${auth}` : null },
});

View File

@@ -4,7 +4,12 @@ import PropTypes from 'prop-types';
import BackIcon from './IconButtons/BackIcon.js'; import BackIcon from './IconButtons/BackIcon.js';
import EventsIcon from './IconButtons/EventsIcon.js'; import EventsIcon from './IconButtons/EventsIcon.js';
export default function HeaderContentLeft({ activeRoute, hasMultipleEvents, navigation }) { export default function HeaderContentLeft({
activeRoute,
hasActiveEvent,
hasMultipleEvents,
navigation,
}) {
const _goBack = () => { const _goBack = () => {
if (hasActiveEvent) { if (hasActiveEvent) {
navigation.goBack(); navigation.goBack();
@@ -32,10 +37,12 @@ export default function HeaderContentLeft({ activeRoute, hasMultipleEvents, navi
HeaderContentLeft.propTypes = { HeaderContentLeft.propTypes = {
activeRoute: PropTypes.string.isRequired, activeRoute: PropTypes.string.isRequired,
hasActiveEvent: PropTypes.bool,
hasMultipleEvents: PropTypes.bool, hasMultipleEvents: PropTypes.bool,
navigation: PropTypes.func.isRequired, navigation: PropTypes.func.isRequired,
}; };
HeaderContentLeft.defaultProps = { HeaderContentLeft.defaultProps = {
hasActiveEvent: false,
hasMultipleEvents: false, hasMultipleEvents: false,
}; };

View File

@@ -6,7 +6,9 @@ import HeaderContentRight from './HeaderContentRight.js';
const matchStateToProps = (state, ownProps) => ({ const matchStateToProps = (state, ownProps) => ({
avatarUrl: getProfileAvatarUrl(state), avatarUrl: getProfileAvatarUrl(state),
hideUserProfileButton: ownProps.navigation.state.routeName === 'Profile', hideUserProfileButton:
['Profile', 'Register', 'SignInOrRegister'].indexOf(ownProps.navigation.state.routeName) >
-1,
}); });
export default connect( export default connect(

View File

@@ -6,15 +6,16 @@ import { Text, TouchableOpacity, View } from 'react-native';
import styles from './EventTitle.styles.js'; import styles from './EventTitle.styles.js';
export default function EventTitle({ action, date, end, name, start }) { export default function EventTitle({ action, date, end, name, start }) {
const _generateEventTitle = () => {
const whenString = `${date} | ${start} - ${end}`; const whenString = `${date} | ${start} - ${end}`;
const _generateEventTitle = () => ( return (
<View style={styles.eventInfo}> <View style={styles.eventInfo}>
<Text style={styles.eventName}>{name}</Text> <Text style={styles.eventName}>{name}</Text>
<Text style={styles.eventDate}>{whenString}</Text> <Text style={styles.eventDate}>{whenString}</Text>
</View> </View>
); );
};
if (action) { if (action) {
return <TouchableOpacity onPress={action}>{_generateEventTitle()}</TouchableOpacity>; return <TouchableOpacity onPress={action}>{_generateEventTitle()}</TouchableOpacity>;

View File

@@ -1,11 +1,17 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getProfileAvatarUrl } from '../../../selectors/profile.js'; import {
getProfileAvatarUrl,
getUserInitials,
isRegisteredAccount,
} from '../../../selectors/profile.js';
import UserProfileButton from './UserProfileButton.js'; import UserProfileButton from './UserProfileButton.js';
const matchStateToProps = (state) => ({ const matchStateToProps = (state) => ({
avatarUrl: getProfileAvatarUrl(state), avatarUrl: getProfileAvatarUrl(state),
initials: getUserInitials(state),
isRegisteredAccount: isRegisteredAccount(state),
}); });
export default connect( export default connect(

View File

@@ -2,11 +2,16 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Image, TouchableOpacity, View } from 'react-native'; import { Image, TouchableOpacity, View } from 'react-native';
import { Icon } from 'react-native-elements'; import { Avatar, Icon } from 'react-native-elements';
import styles from './UserProfileButton.styles.js'; import styles from './UserProfileButton.styles.js';
export default function UserProfileButton({ avatarUrl, navigation }) { export default function UserProfileButton({
avatarUrl,
initials,
isRegisteredAccount,
navigation,
}) {
const _goToProfile = () => { const _goToProfile = () => {
navigation.navigate('Profile'); navigation.navigate('Profile');
return false; return false;
@@ -14,10 +19,12 @@ export default function UserProfileButton({ avatarUrl, navigation }) {
return ( return (
<TouchableOpacity onPress={_goToProfile}> <TouchableOpacity onPress={_goToProfile}>
{avatarUrl !== null ? ( {isRegisteredAccount !== null ? (
<View style={styles.avatarWrap}> avatarUrl !== null ? (
<Image source={{ uri: avatarUrl }} /> <Avatar source={{ uri: avatarUrl }} />
</View> ) : (
<Avatar title={initials} />
)
) : ( ) : (
<Icon name="ei-user" type="evilicons" size={28} /> <Icon name="ei-user" type="evilicons" size={28} />
)} )}
@@ -27,8 +34,12 @@ export default function UserProfileButton({ avatarUrl, navigation }) {
UserProfileButton.propTypes = { UserProfileButton.propTypes = {
avatarUrl: PropTypes.string, avatarUrl: PropTypes.string,
initials: PropTypes.string,
isRegisteredAccount: PropTypes.bool,
}; };
UserProfileButton.propTypes = { UserProfileButton.propTypes = {
avatarUrl: null, avatarUrl: null,
initials: null,
isRegisteredAccount: false,
}; };

View File

@@ -6,9 +6,11 @@ import { formatPrice } from '../../library/helpers.js';
import { StyleSheet, Text } from 'react-native'; import { StyleSheet, Text } from 'react-native';
const AuctionPriceAndBidCount = ({ bidCount, currentPrice }) => { const AuctionPriceAndBidCount = ({ bidCount, currentPrice }) => {
const _getPriceAndBidString = () => `${formatPrice(currentPrice)} (${bidCount} bids)`;
return ( return (
<Text style={styles.currentPriceAndBidCount} numberOfLines={1}> <Text style={styles.currentPriceAndBidCount} numberOfLines={1}>
{`${formatPrice(currentPrice)} (${bidCount} bids)`} {_getPriceAndBidString()}
</Text> </Text>
); );
}; };

View File

@@ -9,7 +9,7 @@ const BidStatus = ({ isBidding, isWinning }) => {
} }
const statusBarStyle = isWinning const statusBarStyle = isWinning
? [styles.bidStatus, styes.isWinning] ? [styles.bidStatus, styles.isWinning]
: [styles.bidStatus, styles.isOutbid]; : [styles.bidStatus, styles.isOutbid];
return ( return (

View File

@@ -3,10 +3,15 @@ import PropTypes from 'prop-types';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
const STRINGS = {
FILTER: 'Filter',
VIEW: 'View',
};
const FilterBar = ({ changeFilterer, changeViewMode, filterMode, viewMode }) => ( const FilterBar = ({ changeFilterer, changeViewMode, filterMode, viewMode }) => (
<View style={styles.filterBar}> <View style={styles.filterBar}>
<Text style={styles.filter}>Filter</Text> <Text style={styles.filter}>{STRINGS.FILTER}</Text>
<Text style={styles.view}>View</Text> <Text style={styles.view}>{STRINGS.VIEW}</Text>
</View> </View>
); );

View File

@@ -33,24 +33,28 @@ export default class EventListItem extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this._viewEventDetail = this._viewEventDetail.bind(this);
} }
_viewEventDetail = () => { getTimeString() {
const { end, start } = this.props;
return `${start} - ${end}`;
}
_viewEventDetail() {
this.props.setActiveEvent(this.props.id); this.props.setActiveEvent(this.props.id);
this.props.navigation.navigate('Event'); this.props.navigation.navigate('Event');
}; }
render() { render() {
const { date, description, end, name, start } = this.props; const { date, description, name } = this.props;
return ( return (
<TouchableOpacity onPress={this._viewEventDetail}> <TouchableOpacity onPress={this._viewEventDetail}>
<View style={styles.rowContainer}> <View style={styles.rowContainer}>
<Text>{name}</Text> <Text>{name}</Text>
<Text>{date}</Text> <Text>{date}</Text>
<Text> <Text>{this.getTimeString()}</Text>
{start} - {end}
</Text>
<Text>{description}</Text> <Text>{description}</Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>

View File

@@ -3,11 +3,12 @@ import PropTypes from 'prop-types';
import { Button, TextInput, View } from 'react-native'; import { Button, TextInput, View } from 'react-native';
export default function LocalLogin({ doLoginAction }) { import styles from './Login.styles.js';
const [ enabled, setEnableSubmit ] = useState(false); export default function LocalLogin({ doLoginAction }) {
const [ password, setPassword ] = useState(null); const [enabled, setEnableSubmit] = useState(false);
const [ username, setUsername ] = useState(null); const [password, setPassword] = useState(null);
const [username, setUsername] = useState(null);
const _handleLoginSubmit = () => { const _handleLoginSubmit = () => {
doLoginAction(username, password); doLoginAction(username, password);
@@ -33,7 +34,7 @@ export default function LocalLogin({ doLoginAction }) {
keyboardType="email-address" keyboardType="email-address"
onChangeText={(text) => _updateState('username', text)} onChangeText={(text) => _updateState('username', text)}
placeholder="email" placeholder="email"
style={{height: 40}} style={styles.textInput}
value={username} value={username}
/> />
<TextInput <TextInput
@@ -41,14 +42,10 @@ export default function LocalLogin({ doLoginAction }) {
onChangeText={(text) => _updateState('password', text)} onChangeText={(text) => _updateState('password', text)}
placeholder="password" placeholder="password"
secureTextEntry secureTextEntry
style={{height: 40}} style={styles.textInput}
value={password} value={password}
/> />
<Button <Button disabled={!enabled} onPress={_handleLoginSubmit} title="Login" />
disabled={!enabled}
onPress={_handleLoginSubmit}
title="Login"
/>
</View> </View>
); );
} }

View File

@@ -0,0 +1,8 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
flex: 1,
},
textInput: {},
});

View File

@@ -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);

View 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?',
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!',
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(false);
const _handleEndEditing = (nomDeBid) => {
getNomAvailaibility(nomDeBid).then((result) => {
setValidNom(result.available);
if (isStandalone) {
updateNomDeBid(nomDeBid);
}
});
};
const _handleSubmitNom = () => {
if (isNomValid) {
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
onChangeText={(text) => setNomDeBid(text)}
onEndEditing={(text) => _handleEndEditing(text)}
placeholder="nom de bid"
style={[styles.textInput, styles.requiredInput]}
value={newNom}
/>
{!isStandalone && (
<Button
title={STRINGS.SUBMIT_NOM}
onPress={_handleSubmitNom}
disabled={!isNomValid}
/>
)}
</View>
);
}
EditNomDeBid.propTypes = {
isGeneratedNomDeBid: PropTypes.bool,
isStandalone: PropTypes.bool,
nomDeBid: PropTypes.string,
updateNomDeBid: PropTypes.func.isRequired,
};
EditNomDeBid.defaultProps = {
isGeneratedNomDeBid: false,
isStandalone: false,
nomDeBid: null,
};

View File

@@ -1,9 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getProfile } from '../../selectors/profile.js';
import { commonProfileStateToProps } from './Profile.container.js'; import { commonProfileStateToProps } from './Profile.container.js';
import EditProfile from './EditProfile.js'; import EditProfile from './EditProfile.js';
const matchStateToProps = (dispatch) => { const matchStateToProps = (state) => {
const commonProps = commonProfileStateToProps(state); const commonProps = commonProfileStateToProps(state);
const profile = getProfile(state); const profile = getProfile(state);
@@ -14,4 +15,7 @@ const matchStateToProps = (dispatch) => {
}; };
}; };
export default connect(matchStateToProps, null)(EditProfile); export default connect(
matchStateToProps,
null,
)(EditProfile);

View File

@@ -1,19 +1,19 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Text, TextInput, View } from 'react-native'; import PropTypes from 'prop-types';
import { Button, Text, TextInput, View } from 'react-native';
import { Avatar } from 'react-native-elements'; import { Avatar } from 'react-native-elements';
import { getEmailAvailability, getNomAvailability } from '../api/profile.js'; import { getEmailAvailability, getNomAvailability } from '../api/profile.js';
import EditNomDeBid from './EditNomDeBid.js';
import styles from './Profile.styles.js'; import styles from './Profile.styles.js';
const STRINGS = { const STRINGS = {
CANCEL: 'Cancel', 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', SAVE_PROFILE: 'Save profile',
}; };
export default class EditProfile extends Component { export default class EditProfile extends Component {
static get propTypes() { static get propTypes() {
return { return {
addresses: PropTypes.array, addresses: PropTypes.array,
@@ -22,6 +22,7 @@ export default class EditProfile extends Component {
email: PropTypes.string, email: PropTypes.string,
firstName: PropTypes.string, firstName: PropTypes.string,
initials: PropTypes.string, initials: PropTypes.string,
isGeneratedNomDeBid: PropTypes.bool,
lastName: PropTypes.string, lastName: PropTypes.string,
nomDeBid: PropTypes.string, nomDeBid: PropTypes.string,
phones: PropTypes.array, phones: PropTypes.array,
@@ -37,6 +38,7 @@ export default class EditProfile extends Component {
email: null, email: null,
firstName: null, firstName: null,
initials: null, initials: null,
isGeneratedNomDeBid: false,
lastName: null, lastName: null,
nomDeBid: null, nomDeBid: null,
phones: null, phones: null,
@@ -44,7 +46,7 @@ export default class EditProfile extends Component {
}; };
} }
constructor() { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@@ -65,11 +67,15 @@ export default class EditProfile extends Component {
} }
_validateEmail() { _validateEmail() {
getEmailAvailability(this.state.email, (result) => this.setState('invalidEmail', !result.available)); getEmailAvailability(this.state.email, (result) =>
this.setState('invalidEmail', !result.available),
);
} }
_validateNomDeBid() { _validateNomDeBid() {
getNomAvailability(this.state.nomDeBid, (result) => this.setState('invalidNomDeBid', !result.available)); getNomAvailability(this.state.nomDeBid, (result) =>
this.setState('invalidNomDeBid', !result.available),
);
} }
getProfileFromState() { getProfileFromState() {
@@ -100,16 +106,23 @@ export default class EditProfile extends Component {
} }
isFormComplete() { isFormComplete() {
return !this.state.invalidEmail && !this.state.invalidNomDeBid && return (
!!this.state.firstName && !!this.state.lastName && !!this.state.email && !this.state.invalidEmail &&
!!this.state.nomDeBid && !!this.state.phones.length && !!this.state.password; !this.state.invalidNomDeBid &&
!!this.state.firstName &&
!!this.state.lastName &&
!!this.state.email &&
!!this.state.nomDeBid &&
!!this.state.phones.length &&
!!this.state.password
);
} }
render() { render() {
const { isGeneratedNomDeBid } = this.props;
const { avatar, firstName, lastName } = this.state; const { avatar, firstName, lastName } = this.state;
const avatarTitle = !avatar && firstName && lastName const addressesTitle = 'Addresses';
? `${firstName.substring(0,1)}${lastName.substring(0,1)}` const numbersTitle = 'Numbers';
: null;
return ( return (
<View style={styles.profileFormWrap}> <View style={styles.profileFormWrap}>
@@ -144,29 +157,31 @@ export default class EditProfile extends Component {
value={this.state.email} value={this.state.email}
/> />
</View> </View>
{isGeneratedNomDeBid && (
<View style={[styles.nomWrap, styles.requiredWrap]}> <View style={[styles.nomWrap, styles.requiredWrap]}>
<Text style={styles.hintText}>{STRINGS.NOM_EXPLANATION}</Text> <EditNomDeBid
<TextInput isGeneratedNomDeBid={isGeneratedNomDeBid}
onChangeText={(text) => this.setState({ nomDeBid: text })} isStandalone={false}
onEndEditing={(text) => this._validateEmail(text)} nomDeBid={this.state.nomDeBid}
placeholder="nom de bid" updateNomDeBid={(nomDeBid) => this.setState({ nomDeBid })}
style={[styles.textInput, styles.requiredInput]}
value={this.state.nomDeBid}
/> />
</View> </View>
<View style={styles.phonesWrap}>
<Text style={[styles.groupLabel, styles.requiredLabel]}>Numbers</Text>
{phones.length > 0 && (
// LIST PHONES
)} )}
<Button title="Add number" onPress={() => false}/> <View style={styles.phonesWrap}>
<Text style={[styles.groupLabel, styles.requiredLabel]}>{numbersTitle}</Text>
{this.props.phones.length > 0 && (
/* LIST PHONES */
<View />
)}
<Button title="Add number" onPress={() => false} />
</View> </View>
<View style={styles.addressesWrap}> <View style={styles.addressesWrap}>
<Text style={styles.groupLabel}>Addresses</Text> <Text style={styles.groupLabel}>{addressesTitle}</Text>
{addresses.length > 0 && ( {this.props.addresses.length > 0 && (
// LIST ADDRESSES /* LIST ADDRESSES */
<View />
)} )}
<Button title="Add address" onPress={() => false}/> <Button title="Add address" onPress={() => false} />
</View> </View>
<View style={styles.register}> <View style={styles.register}>
<Button title={this.props.saveProfileLabel} onPress={this.handleSubmit} /> <Button title={this.props.saveProfileLabel} onPress={this.handleSubmit} />

View File

@@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getProfile } from '../selectors/profile.js'; import { getProfile, isGeneratedNomDeBid } from '../selectors/profile.js';
import Profile from './Profile.js'; import Profile from './Profile.js';
@@ -12,9 +12,13 @@ export const commonProfileStateToProps = (state) => {
avatar: profile.get('avatar'), avatar: profile.get('avatar'),
email: profile.get('email'), email: profile.get('email'),
initials: profile.get('initials'), initials: profile.get('initials'),
isGeneratedNomDeBid: isGeneratedNomDeBid(state),
nomDeBid: profile.get('nomDeBid'), nomDeBid: profile.get('nomDeBid'),
phones: profile.get('phones'), phones: profile.get('phones'),
}; };
}; };
export default connect(commonProfileStateToProps, null)(Profile); export default connect(
commonProfileStateToProps,
null,
)(Profile);

View File

@@ -1,4 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native'; import { View } from 'react-native';
import EditProfile from './EditProfile.container.js'; import EditProfile from './EditProfile.container.js';
@@ -20,7 +21,7 @@ export default function Profile({
const _saveProfileAction = (profile) => { const _saveProfileAction = (profile) => {
setEditMode(false); setEditMode(false);
saveProfileAction(profile); saveProfileAction(profile);
} };
return ( return (
<View> <View>

View File

@@ -0,0 +1,8 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
flex: 1,
},
textInput: {},
});

View File

@@ -1,17 +1,23 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getProfile } from '../../selectors/profile.js';
import { commonProfileStateToProps } from './Profile.container.js'; import { commonProfileStateToProps } from './Profile.container.js';
import { isRegisteredAccount } from '../../selectors/profile.js';
import ViewProfile from './ViewProfile.js'; import ViewProfile from './ViewProfile.js';
const matchStateToProps = (dispatch) => { const matchStateToProps = (state) => {
const commonProps = commonProfileStateToProps(state); const commonProps = commonProfileStateToProps(state);
const profile = getProfile(state); const profile = getProfile(state);
return { return {
...commonProps, ...commonProps,
fullName: profile.get('fullName'), fullName: profile.get('fullName'),
generatedNomDeBid: propfile.get('generatedNomDeBid'), isRegisteredAccount: isRegisteredAccount(state),
}; };
}; };
export default connect(matchStateToProps, null)(ViewProfile); export default connect(
matchStateToProps,
null,
)(ViewProfile);

View File

@@ -1,8 +1,9 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Text, TextInput, View } from 'react-native'; import PropTypes from 'prop-types';
import { Button, Text, TextInput, View } from 'react-native';
import { Avatar } from 'react-native-elements'; import { Avatar } from 'react-native-elements';
import { getEmailAvailability, getNomAvailability } from '../api/profile.js'; import EditNomDeBid from './EditNomDeBid.container.js';
import styles from './Profile.styles.js'; import styles from './Profile.styles.js';
@@ -16,16 +17,22 @@ export default function ViewProfile({
editProfileAction, editProfileAction,
email, email,
fullName, fullName,
generatedNomDeBid,
initials, initials,
isGeneratedNomDeBid,
isRegisteredAccount,
nomDeBid, nomDeBid,
phones, phones,
}) { }) {
const addressesCount = addresses.length; const _getSavedText = (count) => `${count} saved`;
const phonesCount = phones.length; const addressesCountString = _getSavedText(addresses.length);
const phonesCountString = _getSavedText(phones.length);
const [isEditingNom, setEditNom] = useState(false); const [isEditingNom, setEditNom] = useState(false);
const addressesTitle = 'addresses';
const emailTitle = 'email';
const numbersTitle = 'numbers';
return ( return (
<View style={styles.profileFormWrap}> <View style={styles.profileFormWrap}>
<View style={styles.avatarWrap}> <View style={styles.avatarWrap}>
@@ -39,33 +46,30 @@ export default function ViewProfile({
<Text style={styles.fullName}>{fullName}</Text> <Text style={styles.fullName}>{fullName}</Text>
<View style={styles.nomWrap}> <View style={styles.nomWrap}>
{isEditingNom ? ( {isEditingNom ? (
<EditNomDeBid <EditNomDeBid isStandalone />
) : ( ) : (
<Text style={styles.nom}>{nomDeBid}</Text> <Text style={styles.nom}>{nomDeBid}</Text>
{generatedNomDeBid && (
<Button
title="Set bidding alias"
onPress={() => setEditNom(true)}
/>
)} )}
{!isEditingNom && isGeneratedNomDeBid && isRegisteredAccount && (
<Button title="Set bidding alias" onPress={() => setEditNom(true)} />
)} )}
</View> </View>
</View> </View>
<View style={styles.emailWrap}> <View style={styles.emailWrap}>
<Text style={styles.label}>email</Text> <Text style={styles.label}>{emailTitle}</Text>
<text style={styles.value}>{email}</Text> <Text style={styles.value}>{email}</Text>
</View> </View>
<View style={styles.phonesWrap}> <View style={styles.phonesWrap}>
<Text style={styles.label}>numbers</Text> <Text style={styles.label}>{numbersTitle}</Text>
<Text style={styles.value}>{`${phonesCount} saved`} <Text style={styles.value}>{phonesCountString}</Text>
</View> </View>
<View style={styles.addressesWrap}> <View style={styles.addressesWrap}>
<Text style={styles.label}>addresses</Text> <Text style={styles.label}>{addressesTitle}</Text>
<Text style={styles.value}>{`${addressesCount} saved`} <Text style={styles.value}>{addressesCountString}</Text>
</View> </View>
{editProfileAction !== null && ( {editProfileAction !== null && (
<View style={styles.register}> <View style={styles.register}>
<Button title={STRINGS.EDIT} onPress={editProfile} /> <Button title={STRINGS.EDIT} onPress={editProfileAction} />
</View> </View>
)} )}
</View> </View>
@@ -78,8 +82,9 @@ ViewProfile.propTypes = {
editProfileAction: PropTypes.func, editProfileAction: PropTypes.func,
email: PropTypes.string, email: PropTypes.string,
fullName: PropTypes.string, fullName: PropTypes.string,
generatedNomDeBid: PropTypes.bool,
initials: PropTypes.string, initials: PropTypes.string,
isGeneratedNomDeBid: PropTypes.bool,
isRegisteredAccount: PropTypes.bool,
nomDeBid: PropTypes.string, nomDeBid: PropTypes.string,
phones: PropTypes.array, phones: PropTypes.array,
}; };
@@ -90,13 +95,9 @@ ViewProfile.defaultProps = {
editProfileAction: null, editProfileAction: null,
email: null, email: null,
fullName: null, fullName: null,
generatedNomDeBid: false,
initials: null, initials: null,
isGeneratedNomDeBid: false,
isRegisteredAccount: false,
nomDeBid: null, nomDeBid: null,
phones: [], phones: [],
}; };
return (
);
}
}

View File

@@ -50,6 +50,9 @@ export const UNSET_PROFILE = 'UNSET_PROFILE';
export const UPDATE_PROFILE = 'UPDATE_PROFILE'; export const UPDATE_PROFILE = 'UPDATE_PROFILE';
export const SET_NOM_DE_BID = 'SET_NOM_DE_BID'; export const SET_NOM_DE_BID = 'SET_NOM_DE_BID';
export const SET_NOM_FAILURE = 'SET_NOM_FAILURE';
export const SET_NOM_SUCCESS = 'SET_NOM_SUCCESS';
export const SET_PASSWORD = 'SET_PASSWORD'; export const SET_PASSWORD = 'SET_PASSWORD';
export const ADD_PAYMENT_DATA = 'ADD_PAYMENT_DATA'; export const ADD_PAYMENT_DATA = 'ADD_PAYMENT_DATA';

View File

@@ -27,5 +27,5 @@ const mapDispatchToProps = (dispatch) => ({
export default connect( export default connect(
mapStateToProps, mapStateToProps,
null, mapDispatchToProps,
)(AuctionListItem); )(EventListItem);

View File

@@ -29,8 +29,8 @@ export default class Profile extends Record({
} }
get initials() { get initials() {
const firstInitial = this.firstName ? this.firstName.substring(0,1) : null; const firstInitial = this.firstName ? this.firstName.substring(0, 1) : null;
const lastInitial = this.firstName ? this.firstName.substring(0,1) : null; const lastInitial = this.firstName ? this.firstName.substring(0, 1) : null;
if (!firstInitial && !lastInitial) { if (!firstInitial && !lastInitial) {
return null; return null;

View File

@@ -150,43 +150,3 @@ export const Tabs = createBottomTabNavigator({
}, },
}, },
}); });
export const createRootNavigator = () => {
return StackNavigator(
{
AuctionStack: {
screen: AuctionStack,
navigationOptions: {
gesturesEnabled: false,
},
},
BazaarStack: {
screen: BazaarStack,
navigationOptions: {
gesturesEnabled: false,
},
},
EventsStack: {
screen: EventsStack,
navigationOptions: {
gesturesEnabled: false,
},
},
SignInOrRegisterStack: {
screen: SignInOrRegister,
navigationOptions: {
gesturesEnabled: false,
},
},
Tabs: {
screen: Tabs,
navigationOptions: {
gesturesEnabled: false,
},
},
},
{
mode: 'modal',
},
);
};

View File

@@ -7,17 +7,16 @@ import { getAuctionItemsAsList } from '../selectors/items.js';
import Auction from './Auction.js'; import Auction from './Auction.js';
const matchStateToProps = (state) => { const changeViewMode = () => true;
const items = getAuctionItemsAsList(state);
console.log('items:', items);
return { items }; const matchStateToProps = (state) => ({
}; items: getAuctionItemsAsList(state),
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
changeViewMode: (mode) => dispatch(changeViewMode(mode)), changeViewMode: (mode) => dispatch(changeViewMode(mode)),
fetchItems: () => dispatch(fetchItems(dispatch)), fetchItems: () => dispatch(fetchItems()),
fetchStatus: () => dispatch(fetchAuctionStatus(dispatch)), fetchStatus: () => dispatch(fetchAuctionStatus()),
}); });
export default connect( export default connect(

View File

@@ -1,6 +1,6 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
export default (styles = StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
@@ -14,4 +14,4 @@ export default (styles = StyleSheet.create({
alignItems: 'stretch', alignItems: 'stretch',
justifyContent: 'flex-start', justifyContent: 'flex-start',
}, },
})); });

View File

@@ -3,9 +3,11 @@ import { StyleSheet, Text, View } from 'react-native';
export default class Checkout extends Component { export default class Checkout extends Component {
render() { render() {
const title = 'Checkout';
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Checkout</Text> <Text style={styles.title}>{title}</Text>
</View> </View>
); );
} }

View File

@@ -73,7 +73,7 @@ export default class Event extends Component {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Event</Text> <Text style={styles.title}>{title}</Text>
</View> </View>
); );
} }

View File

@@ -1,6 +1,6 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
export default (styles = StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
@@ -12,4 +12,4 @@ export default (styles = StyleSheet.create({
textAlign: 'center', textAlign: 'center',
margin: 10, margin: 10,
}, },
})); });

View File

@@ -2,7 +2,7 @@ import { List } from 'immutable';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FlatList, StyleSheet, Text, View } from 'react-native'; import { FlatList, StyleSheet, View } from 'react-native';
import EventListItem from '../components/Events/EventListItem.js'; import EventListItem from '../components/Events/EventListItem.js';

View File

@@ -3,9 +3,11 @@ import { StyleSheet, Text, View } from 'react-native';
export default class ImageDetail extends Component { export default class ImageDetail extends Component {
render() { render() {
const title = 'Item';
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Item</Text> <Text style={styles.title}>{title}</Text>
</View> </View>
); );
} }

View File

@@ -3,9 +3,11 @@ import { StyleSheet, Text, View } from 'react-native';
export default class Item extends Component { export default class Item extends Component {
render() { render() {
const title = 'Item';
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Item</Text> <Text style={styles.title}>{title}</Text>
</View> </View>
); );
} }

View File

@@ -1,26 +0,0 @@
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class Login extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>Login</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
title: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});

View File

@@ -9,7 +9,7 @@ import { SORT_MODES, AUCTION_VIEW_MODES } from '../constants/constants.js';
import FilterBar from '../components/Auction/FilterBar.js'; import FilterBar from '../components/Auction/FilterBar.js';
import AuctionListItem from '../containers/Auction/AuctionListItem.js'; import AuctionListItem from '../containers/Auction/AuctionListItem.js';
//import styles from './Marketplace.styles.js'; import styles from './Auction.styles.js';
export default class Marketplace extends Component { export default class Marketplace extends Component {
static get propTypes() { static get propTypes() {

View File

@@ -1,12 +1,16 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import ProfileUtility from '../components/Profile/Profile.container.js'; import ProfileUtility from '../components/Profile/Profile.container.js';
import styles from './Profile.styles.js'; import styles from './Profile.styles.js';
export default class Profile extends Component { const STRINGS = {
EMAIL_NEEDS_VERIFICATION: 'Your acount has not been verified, please check your email.',
};
export default class Profile extends Component {
static get propTypes() { static get propTypes() {
return { return {
hasLinkedApple: PropTypes.bool, hasLinkedApple: PropTypes.bool,
@@ -48,9 +52,7 @@ export default class Profile extends Component {
<View style={styles.container}> <View style={styles.container}>
{!isVerified && ( {!isVerified && (
<View style={styles.alertBar}> <View style={styles.alertBar}>
<Text style={styles.alert}> <Text style={styles.alert}>{STRINGS.EMAIL_NEEDS_VERIFICATION}</Text>
{`Your acount has not been verified, please check your email.`}
</Text>
</View> </View>
)} )}
@@ -62,24 +64,30 @@ export default class Profile extends Component {
{!isAllowedToBid ? ( {!isAllowedToBid ? (
/* ADD PAYMENT METHOD */ /* ADD PAYMENT METHOD */
<View />
) : ( ) : (
/* SHOW/EDIT PAYMENT METHOD */ /* SHOW/EDIT PAYMENT METHOD */
<View />
)} )}
{!hasLocalAccount && ( {!hasLocalAccount && (
/* CREATE LOCAL ACCOUNT PASSWORD CTA */ /* CREATE LOCAL ACCOUNT PASSWORD CTA */
<View />
)} )}
{hasLinkedApple && ( {hasLinkedApple && (
/* APPLE LINK/UNLINK */ /* APPLE LINK/UNLINK */
<View />
)} )}
{hasLinkedFacebook && ( {hasLinkedFacebook && (
/* FACEBOOK LINK/UNLINK */ /* FACEBOOK LINK/UNLINK */
<View />
)} )}
{hasLinkedGoogle && ( {hasLinkedGoogle && (
/* GOOGLE LINK/UNLINK */ /* GOOGLE LINK/UNLINK */
<View />
)} )}
</View> </View>
); );

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import EditProfile from '../components/Profile/EditProfile.container.js'; import EditProfile from '../components/Profile/EditProfile.container.js';
@@ -6,6 +7,7 @@ import EditProfile from '../components/Profile/EditProfile.container.js';
import styles from './Register.styles.js'; import styles from './Register.styles.js';
export default function Register({ doRegistration, navigation }) { export default function Register({ doRegistration, navigation }) {
const title = 'Register';
const _doRegistration = (profile) => { const _doRegistration = (profile) => {
if (!profile) { if (!profile) {
@@ -17,10 +19,10 @@ export default function Register({ doRegistration, navigation }) {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.heading}>Register</Text> <Text style={styles.heading}>{title}</Text>
<EditProfile <EditProfile
cancelEditProfile={() => navigation.goBack()} cancelEditProfile={() => navigation.goBack()}
saveProfileAction={doRegistration} saveProfileAction={_doRegistration}
saveProfileLabel="Register" saveProfileLabel="Register"
/> />
</View> </View>

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Text, View } from 'react-native'; import { Button, Text, View } from 'react-native';
import FacebookLogin from '../components/Login/FacebookLogin.container.js'; import FacebookLogin from '../components/Login/FacebookLogin.container.js';
import LocalLogin from '../components/Login/LocalLogin.container.js'; import LocalLogin from '../components/Login/LocalLogin.container.js';
@@ -7,9 +7,11 @@ import LocalLogin from '../components/Login/LocalLogin.container.js';
import styles from './SignInOrRegister.styles.js'; import styles from './SignInOrRegister.styles.js';
export default function SignInOrRegister({ navigation }) { export default function SignInOrRegister({ navigation }) {
const title = 'Sign In or Register';
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Sign In or Register</Text> <Text style={styles.title}>{title}</Text>
<View style={styles.localLogin}> <View style={styles.localLogin}>
<LocalLogin /> <LocalLogin />
</View> </View>

View File

@@ -1,6 +1,6 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
export default (styles = StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
@@ -12,4 +12,4 @@ export default (styles = StyleSheet.create({
textAlign: 'center', textAlign: 'center',
margin: 10, margin: 10,
}, },
})); });

View File

@@ -3,9 +3,11 @@ import { StyleSheet, Text, View } from 'react-native';
export default class Ticketing extends Component { export default class Ticketing extends Component {
render() { render() {
const title = 'Ticketing';
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.title}>Ticketing</Text> <Text style={styles.title}>{title}</Text>
</View> </View>
); );
} }

View File

@@ -1,3 +1,4 @@
import { Map } from 'immutable';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
const getState = (state) => state; const getState = (state) => state;
@@ -16,7 +17,7 @@ export const getAuctionStatus = (state, itemId) => state.getIn(['actions', itemI
export const getAuctionStatuses = createSelector( export const getAuctionStatuses = createSelector(
[getState], [getState],
(state) => state.get('actions') || new Map(), (state) => state.get('autions') || new Map(),
); );
export const getItemsIdsWithNoBids = createSelector( export const getItemsIdsWithNoBids = createSelector(

View File

@@ -18,7 +18,27 @@ export const getProfileAvatarUrl = createSelector(
(profile) => profile.get('avatar'), (profile) => profile.get('avatar'),
); );
export const getUserId = createSelector(
[getProfile],
(profile) => profile.get('id'),
);
export const getUserInitials = createSelector(
[getProfile],
(profile) => profile.get('initials'),
);
export const isAllowedToBid = createSelector( export const isAllowedToBid = createSelector(
[getProfile], [getProfile],
(profile) => profile.get('isAllowedToBid'), (profile) => profile.get('isAllowedToBid'),
); );
export const isGeneratedNomDeBid = createSelector(
[getProfile],
(profile) => profile.get('generatedNomDeBid'),
);
export const isRegisteredAccount = createSelector(
[getProfile],
(profile) => profile.get('isRegisteredAccount'),
);