This commit is contained in:
2019-08-05 21:23:17 -04:00
parent a9f4324f29
commit 1e464de7e8
42 changed files with 837 additions and 165 deletions

View File

@@ -1,8 +1,23 @@
import { blockUI, unblockUI } from './index.js';
import {
BID_FAILURE,
BID_SUCCESS,
PLACE_BID,
SET_AUCTION_FILTER,
SET_AUCTION_VIEW_MODE,
} from '../constants/actionTypes.js';
const placeBidFailure = (bid, dispatch) => {
dispatch({ type: BID_FAILURE, bid });
dispatch(unblockUI);
};
const placeBidSuccess = (bid, dispatch) => {
dispatch({ type: BID_SUCCESS, bid });
dispatch(unblockUI);
};
export const changeFilterMode = (payload) => ({
type: SET_AUCTION_FILTER,
payload,
@@ -13,3 +28,25 @@ export const changeViewMode = (payload) => ({
payload,
});
export const placeBid = (payload) => ({
type: PLACE_BID,
payload,
});
export const postBid = () => (dispatch, getState) => {
const state = getState();
const activeEvent = state.get('activeEvent');
let apiUrl = getEndpointUrl(API_ENDPOINTS.GET_ITEMS);
apiUrl = apiUrl.replace(/:event_id$/, '');
if (activeEvent) {
apiUrl = `${apiUrl}${activeEvent}`;
}
dispatch(blockUI());
fetch(apiUrl)
.then(response => response.json())
.then(payload => itemsLoadSuccess(payload, dispatch))
.catch(err => console.error('[actions::getItems]', err));
};

View File

@@ -19,6 +19,11 @@ const eventsLoadSuccess = (events, dispatch) => {
dispatch(unblockUI);
};
export const setActiveEvent = (eventId) => ({
type: SET_ACTIVE_EVENT,
payload: eventId,
});
export const fetchEvents = () => (dispatch) => {
dispatch(blockUI());
fetch(getEndpointUr(API_ENDPOINTS.GET_EVENTS))

33
app/actions/profile.js Normal file
View File

@@ -0,0 +1,33 @@
import { List } from 'immutable';
import { getEndpointUrl } from '../api/index.js';
import {
EVENTS_LOADED,
GET_EVENTS,
} from '../constants/actionTypes.js';
import { blockUI, unblockUI } from './index.js';
import { API_ENDPOINTS } from '../constants/constants.js';
import Event from '../domain/Event.js';
const eventsLoadSuccess = (events, dispatch) => {
const payload = List(events).map((i) => Event.fromJS(i));
dispatch({ type: EVENTS_LOADED, payload });
dispatch(unblockUI);
};
export const setActiveEvent = (eventId) => ({
type: SET_ACTIVE_EVENT,
payload: eventId,
});
export const fetchEvents = () => (dispatch) => {
dispatch(blockUI());
fetch(getEndpointUr(API_ENDPOINTS.GET_EVENTS))
.then(response => response.json())
.then(payload => eventsLoadSuccess(payload, dispatch))
.catch(err => console.error('[actions::getEvents]', err));
};

View File

@@ -0,0 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Header } from 'react-native-elements';
import HeaderTitle from './HeaderTitle.container.js';
import HeaderContentLeft from './HeaderContentLeft.container.js';
import HeaderContentRight from './HeaderContentRight.container.js';
import styles from './AppHeader.styles.js';
export default function AppHeader({ navigation }) (
<Header
placement="left"
leftComponent={<HeaderContentRight navigation={navigation} />}
centerComponent={<HeaderTitle navigation={navigation} />}
rightComponent={<HeaderContentLeft navigation={navigation} />}
/>
)
AppHeader.propTypes = {
navigation: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import { hasMultipleEvents } from '../selectors/events.js';
import HeaderContentLeft from './HeaderContentLeft.js';
const matchStateToProps = (state, ownProps) => {
const { routeName } = ownProps.navigation.state;
return {
activeRoute: routeName,
hasMultipleEvents: hasMultipleEvents(state),
};
};
export default connect(matchStateToProps, null)(HeaderContentLeft);

View File

@@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
View,
} from 'react-native';
import BackIcon from './IconButtons/BackIcon.js';
import EventsIcon from './IconButtons/EventsIcon.js';
export default function HeaderContentLeft({
activeRoute,
hasMultipleEvents,
navigation,
}) {
const _goBack = () => {
if (hasActiveEvent) {
navigation.goBack();
return false;
}
console.log('nowhere to go...');
};
const _showEvents = () => {
navigation.navigate('Events');
return false;
};
if (activeRoute === 'Events') {
return <EventsIcon action={_goBack} />;
}
if (activeRoute === 'Profile') {
return <BackIcon action={_goBack} />;
}
return <EventsIcon action={hasMultipleEvents ? _showEvents : null} />
}
HeaderContentLeft.propTypes = {
activeRoute: PropTypes.string.isRequired,
hasActiveEvent: PropTypes.bool,
navigation: PropTypes.func.isRequired,
};
HeaderContentLeft.defaultProps = {
hasActiveEvent: false,
};

View File

@@ -0,0 +1,12 @@
import { connect } from 'react-redux';
import { getProfileAvatarUrl } from '../selectors/profile.js';
import HeaderContentRight from './HeaderContentRight.js';
const matchStateToProps = (state, ownProps) => ({
avatarUrl: getProfileAvatarUrl(state),
hideUserProfileButton: ownProps.navigation.state.routeName === 'Profile',
});
export default connect(matchStateToProps, null)(HeaderContentRight);

View File

@@ -0,0 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import UserProfileButton from './UserProfileButton/UserProfileButton.container.js';
export default function HeaderContentRight({ hideUserProfileButton, navigation }) {
if (hideUserProfileButton) {
return null;
}
return <UserProfileButton />;
}
HeaderContentRight.propTypes = {
hideUserProfileButton: PropTypes.bool.isRequired,
};

View File

@@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import { getActiveEvent, getDefaultEvent } from '../selectors/events.js';
import EventTitle from './EventTitle.js';
const matchStateToProps = (state) => {
const event = hasActiveEvent(state) ? getActiveEvent(state) : getDefaultEvent(state);
return {
date: event.get('date'),
end: event.get('end'),
name: event.get('name'),
start: event.get('start'),
};
};
export default connect(matchStateToProps, null)(EventTitle);

View File

@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
View,
} from 'react-native';
import styles from './EventTitle.styles.js';
export default function EventTitle({
action,
date,
end,
name,
start,
}) {
const _generateEventTitle = () => (
<View style={styles.eventInfo}>
<Text style={styles.eventName}>{name}</Text>
<Text style={styles.eventDate}>{`${date} | ${start} - ${end}`}</Text>
</View>
);
if (action) {
return <TouchableOpacity onPress={action}>{_generateEventTitle()}</TouchableOpacity>;
}
return _generateEventTitle();
}
EventTitle.propTypes = {
action: PropTypes.func,
date: PropTypes.string.isRequired,
end: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
start: PropTypes.string.isRequired,
};
EventTitle.defaultProps = {
action: null,
};

View File

@@ -0,0 +1,15 @@
import { StyleSheet } from 'react-native';
export default const styles = StyleSheet.create({
eventInfo: {
flexDirection: 'row',
},
eventName: {
flex: 1,
fontWeight: 'bold',
},
eventDate: {
flex: 1,
},
});

View File

@@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import { hasActiveEvent } from '../selectors/activeEvent.js';
import { hasMultipleEvents } from '../selectors/events.js';
import HeaderTitle from './HeaderTitle.js';
const matchStateToProps = (state, ownProps) => {
const { routeName } = ownProps.navigation.state;
return {
activeRoute: routeName,
hasActiveEvent: hasActiveEvent(state),
hasMultipleEvents: hasMultipleEvents(state),
};
};
export default connect(matchStateToProps, null)(HeaderTitle);

View File

@@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
View,
} from 'react-native';
import EventTitle from './EventTitle/EventTitle.container.js';
import styles from './TitleBar.styles.js';
export default function HeaderTitle({
activeRoute,
hasActiveEvent,
hasMultipleEvents,
navigation,
}) {
const _goBack = () => {
if (hasActiveEvent) {
navigation.goBack();
return false;
}
console.log('nowhere to go...');
};
const _showEvents = () => {
navigation.navigate('Events');
return false;
};
if (activeRoute === 'Events') {
return (
<TouchableOpacity onPress={_goBack}>
<Text style={styles.screenHeader}>Profile</Text>
</TouchableOpacity>
);
}
if (activeRoute === 'Profile') {
return <Text style={styles.screenHeader}>Profile</Text>;
}
return <EventTitle action={hasMultipleEvents ? _showEvents : null} />
}
HeaderTitle.propTypes = {
activeRoute: PropTypes.string.isRequired,
hasActiveEvent: PropTypes.bool,
hasMultipleEvents: PropTypes.bool.isRequired,
navigation: PropTypes.func.isRequired,
};
HeaderTitle.defaultProps = {
hasActiveEvent: false,
};

View File

@@ -0,0 +1,15 @@
import { StyleSheet } from 'react-native';
export default const styles = StyleSheet.create({
filterBar: {
backgroundColor: '#0F0',
flexDirection: 'row',
},
filter: {
flex: 2,
},
view: {
flex: 2,
},
});

View File

@@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
export default function BackIcon({ action }) (
<TouchableOpacity onPress={action}>
<Icon name="ei-chevron-left" type="evilicons" size={28} />;
</TouchableOpacity>
)
BackIcon.propTypes = {
action: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
export default function EventsIcon({ action }) {
const renderEventsIcon = () => <Icon name="ei-calendar" type="evilicons" size={28} />;
if (action) {
return <TouchableOpacity onPress={action}>{renderEventsIcon()}</TouchableOpacity>;
}
return renderEventsIcon();
}
EventsIcon.propTypes = {
action: PropTypes.func,
};
EventsIcon.defaultProps = {
action: null,
};

View File

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

View File

@@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Image, TouchableOpacity, View } from 'react-native';
import { Icon } from 'react-native-elements';
import styles from './UserProfileButton.styles.js';
export default function UserProfileButton({ avatarUrl, navigation }) {
const _goToProfile = () => {
navigation.navigate('Profile');
return false;
};
return (
<TouchableOpacity onPress={_goToProfile}>
{avatarUrl !== null ? (
<View style={styles.avatarWrap}>
<Image source={{ uri: avatarUrl }} />
</View>
) : (
<Icon name="ei-user" type="evilicons" size={28} />;
)}
</TouchableOpacity>
);
}
HeaderContentRight.propTypes = {
avatarUrl: PropTypes.string,
};
HeaderContentRight.propTypes = {
avatarUrl: null,
};

View File

@@ -17,7 +17,7 @@ import BidStatus from '../../containers/Auction/BidStatus.js';
import { ITEM_TYPES } from '../../constants/constants.js';
import { formatPrice, getAuctionTime } from '../../library/helpers.js';
export default class ItemRow extends Component {
export default class AuctionListItem extends Component {
static get propTypes() {
return {
description: PropTypes.string,

View File

@@ -0,0 +1,84 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
StyleSheet,
TouchableOpacity,
Text,
Image,
View
} from 'react-native';
export default class EventListItem extends Component {
static get propTypes() {
return {
description: PropTypes.string.isRequired,
endTime: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
images: PropTypes.arrayOf(
PropTypes.shape({
url: PropTypes.string,
}),
),
isTicketed: PropTypes.bool,
postCount: PropTypes.number,
setActiveEvent: PropTypes.func.isRequired,
showFrom: PropTypes.string.isRequired,
showUntil: PropTypes.string.isRequired,
startTime: PropTypes.string.isRequired,
tagline: PropTypes.string,
title: PropTypes.string.isRequired,
};
}
static get defaultProps() {
return {
images: null,
isTicketed: false,
postCount: 0,
tagline: null,
};
}
constructor(props) {
super(props);
}
_viewEventDetail = () => {
const { id } = this.props.details;
this.props.setActiveEvent(id);
this.props.navigation.navigate('Event');
}
render() {
const {
} = this.props;
return(
<TouchableOpacity onPress={this._viewEventDetail}>
<View style={styles.rowContainer}>
</View>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
rowContainer: {
backgroundColor: '#FFF',
borderRadius: 4,
flex: 1,
flexDirection: 'column',
marginRight: 10,
marginLeft: 10,
marginTop: 10,
padding: 10,
shadowColor: '#CCC',
shadowOffset: {
width: 1,
height: 1
},
shadowOpacity: 1.0,
shadowRadius: 1,
},
});

View File

@@ -12,6 +12,8 @@ export const AUCTIONS_UPDATED = 'AUCTIONS_UPDATED';
export const SET_TICKET_PURCHASE_FLOW = 'SET_TICKET_PURCHASE_FLOW';
export const PLACE_BID = 'PLACE_BID';
export const BID_FAILURE = 'BID_FAILURE';
export const BID_SUCCESS = 'BID_SUCCESS';
export const DO_LOGIN = 'DO_LOGIN';
export const DO_LOGOUT = 'DO_LOOUT';

View File

@@ -1,4 +1,10 @@
export const matchStateToProps = (state, ownProps) => {
import { connect } from 'react-redux';
import { placeBid } from '../../actions/auction.js';
import AuctionListItem from '../../components/Auction/AuctionListItem.js';
const mapStateToProps = (state, ownProps) => {
const { item } = ownProps;
return {
@@ -14,3 +20,10 @@ export const matchStateToProps = (state, ownProps) => {
type: item.get('type'),
};
};
const mapDispatchToProps = (dispatch) => ({
placeBid: (data) => dispatch(placeBid(data)),
});
export default connect(mapStateToProps, null)(AuctionListItem);

View File

@@ -0,0 +1,29 @@
import { connect } from 'react-redux';
import { setActiveEvent } from '../../actions/events.js';
import EventListItem from '../../components/Events/EventListItem.js';
const mapStateToProps = (state, ownProps) => {
const { event } = ownProps;
return {
description: event.get('description'),
endTime: event.get('endTime'),
id: event.get('id'),
images: event.get('images').toArray(),
isTicketed: event.get('isTicketed'),
postCount: event.get('posts').size,
showFrom: event.get('showFrom'),
showUntil: event.get('showUntil'),
startTime: event.get('startTime'),
tagline: event.get('tagline'),
title: event.get('title'),
};
};
const mapDispatchToProps = (dispatch) => ({
setActiveEvent: (eventId) => dispatch(setActiveEvent(eventId)),
});
export default connect(mapStateToProps, null)(AuctionListItem);

View File

@@ -1,6 +0,0 @@
import { connect } from 'react-redux';
import { mapStateToProps } from './Item.js';
import ItemRow from '../../components/Item/List.js';
export default connect(mapStateToProps, null)(ItemRow);

View File

@@ -27,6 +27,11 @@ export default class Profile extends Record({
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get isRegisteredAccount() {
return this.hasLinkedApple ||
this.hasLinkedFacebook || this.hasLinkedGoogle || this.hasLocalAccount;
}
}
Profile.fromJS = (data = {}) => {

View File

@@ -3,16 +3,18 @@ import { Dimensions, Platform } from 'react-native';
import { createBottomTabNavigator, createStackNavigator } from 'react-navigation';
import { Icon } from 'react-native-elements';
import Auction from './containers/Auction.js';
import AppHeader from './Components/AppHeader/AppHeader.js';
import Auction from './screen/Auction.container.js';
import Checkout from './screens/Checkout.js';
import Event from './screens/Event.js';
import Events from './screens/Events.js';
import Event from './screens/Event.container.js';
import Events from './screens/Events.container.js';
import ImageDetail from './screens/ImageDetail.js';
import Item from './screens/Item.js';
import Marketplace from './screens/Marketplace.js';
import Profile from './screens/Profile.js';
import Profile from './screens/Profile.container.js';
let screen = Dimensions.get('window');
//let screen = Dimensions.get('window');
export const Tabs = createBottomTabNavigator({
'Event': {
@@ -49,7 +51,7 @@ export const AuctionStack = createStackNavigator({
Auction: {
screen: Auction,
navigationOptions: ({navigation}) => ({
header: null,
header: <AppHeader navigation={navigation} />,
}),
},
Item: {
@@ -74,7 +76,7 @@ export const BazaarStack = createStackNavigator({
Bazaar: {
screen: Marketplace,
navigationOptions: ({navigation}) => ({
header: null,
header: <AppHeader navigation={navigation} />,
}),
},
Item: {
@@ -107,7 +109,7 @@ export const EventsStack = createStackNavigator({
Events: {
screen: Events,
navigationOptions: ({ navigation }) => ({
header: null,
header: <AppHeader navigation={navigation} />,
tabBarVisible: false,
gesturesEnabled: false
}),
@@ -129,6 +131,12 @@ export const createRootNavigator = () => {
gesturesEnabled: false
}
},
EventsStack: {
screen: EventsStack,
navigationOptions: {
gesturesEnabled: false
}
},
Tabs: {
screen: Tabs,
navigationOptions: {
@@ -137,7 +145,6 @@ export const createRootNavigator = () => {
}
},
{
headerMode: "none",
mode: "modal"
}
);

View File

@@ -5,7 +5,7 @@ import { fetchAuctionStatus } from '../actions/auctionStatus.js';
import { getAuctionItemsAsList } from '../selectors/items.js';
import Auction from '../screens/Auction.js';
import Auction from './Auction.js';
const matchStateToProps = (state) => {
const items = getAuctionItemsAsList(state);

View File

@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import {
FlatList,
StyleSheet,
Text,
View,
} from 'react-native';
@@ -12,12 +11,15 @@ import {
import { SORT_MODES, AUCTION_VIEW_MODES } from '../constants/constants.js';
import FilterBar from '../components/Auction/FilterBar.js';
import ListItem from '../containers/Item/List.js';
import AuctionListItem from '../containers/Auction/AuctionListItem.js';
import styles from './Auction.styles.js';
export default class Auction extends Component {
static get propTypes() {
return {
changeFilter: PropTypes.func,
changeViewMode: PropTypes.func.isRequired,
fetchItems: PropTypes.func.isRequired,
fetchStatus: PropTypes.func.isRequired,
items: PropTypes.oneOfType([
@@ -55,13 +57,9 @@ export default class Auction extends Component {
this.props.changeFilter('auction', filter);
}
changeViewMode(mode) {
this.setState({ view: mode });
}
_keyExtractor = (item, index) => `${item._id}_${index}`;
_renderItem = ({ item }) => <ListItem item={item} />;
_renderAuctionListItem = ({ item }) => <AuctionListItem item={item} />;
render() {
const { items } = this.props;
@@ -71,13 +69,12 @@ export default class Auction extends Component {
<View style={styles.container}>
<FilterBar
changeFilterer={this.changeFilter}
changeViewMode={this.changeViewMode}
/>
{items.size > 0 && (
<FlatList
data={items}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem(view)}
renderItem={this._renderAuctionListItem}
contentContainerStyle={styles.itemListContentContainer}
style={styles.itemList}
/>
@@ -86,19 +83,3 @@ export default class Auction extends Component {
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
itemList: {
width: '100%',
},
itemListContentContainer: {
alignItems: 'stretch',
justifyContent: 'flex-start',
},
});

View File

@@ -0,0 +1,17 @@
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
itemList: {
width: '100%',
},
itemListContentContainer: {
alignItems: 'stretch',
justifyContent: 'flex-start',
},
});

View File

@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import { fetchEvent } from '../actions/events.js';
import { getEventById } from '../selectors/events.js';
import Event from '../screens/Event.js';
import Event from './Event.js';
const matchStateToProps = (state) => {
const eventId = state.get('activeEvent');

View File

@@ -1,21 +1,80 @@
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
} from 'react-native';
import { Text, View } from 'react-native';
import styles from './Event.styles.js';
export default class Event extends Component {
static get propTypes() {
return {
description: PropTypes.string.isRequired,
endTime: PropTypes.string.isRequired,
fetchEvent: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
images: PropTypes.arrayOf(
PropTypes.shape({
url: PropTypes.string,
}),
),
isTicketed: PropTypes.bool,
posts: PropTypes.arrayOf(
PropTypes.shape({
author: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
isPublic: PropTypes.bool,
scheduledPost: PropTypes.bool,
sendNotification: PropTypes.bool,
timestamp: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
}),
),
requireLoginToSeeAuction: PropTypes.bool.isRequired,
showFrom: PropTypes.string.isRequired,
showUntil: PropTypes.string.isRequired,
startTime: PropTypes.string.isRequired,
tagline: PropTypes.string,
ticketClasses: PropTypes.arrayOf(
PropTypes.shape({
constructor(props) {
super(props);
this.state = {
event,
}),
)
title: PropTypes.string.isRequired,
url: PropTypes.string,
};
}
static get defaultProps() {
return {
images: null,
isTicketed: false,
posts: null,
requireLoginToSeeAuction: false,
tagline: null,
ticketClasses: null,
url: null,
};
}
constructor(props) {
super(props);
}
render() {
const {
description,
endTime,
images,
isTicketed,
requireLoginToSeeAuction,
showFrom,
showUntil,
startTime,
tagline,
ticketClasses,
title,
url,
} = this.props;
return (
<View style={styles.container}>
<Text style={styles.title}>
@@ -25,100 +84,3 @@ export default class Event extends Component {
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
title: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
});
const event = {
isTicketed: true,
requireLoginToSeeAuction: false,
_id: "5d1f0a9289511160ddd681f4",
description: "Cumque rerum nihil sit libero in.. Excepturi molestiae dicta.. Ratione perspiciatis similique et sed corrupti excepturi ut consequatur in.. Impedit voluptate deleniti suscipit vero tenetur.. Aut ut saepe nesciunt voluptas quod nam laudantium unde.",
endTime: "2020-01-02T05:39:39.957Z",
images: [
{
_id: "5d1f0a9289511160ddd681f7",
url: "https://lorempixel.com/640/480/cats?37458"
},
{
_id: "5d1f0a9289511160ddd681f6",
url: "https://lorempixel.com/640/480/cats?80772"
},
{
_id: "5d1f0a9289511160ddd681f5",
url: "https://lorempixel.com/640/480/cats?2451"
}
],
posts: [
{
isPublic: true,
scheduledPost: false,
_id: "5d1f0a9289511160ddd681fb",
author: "Dandre Leuschke",
title: "Facere a atque.",
content: "Reprehenderit minus ut. Quibusdam reiciendis rerum fugit est quis. Omnis architecto magni. Libero et nihil excepturi facere doloribus doloremque in error deleniti. Modi et id sint exercitationem esse tenetur iure. Occaecati aliquid maxime ratione asperiores repudiandae.\n \rQuia ipsam quia fuga pariatur odit sunt. Sed dicta earum officiis quo quo. Sunt maiores itaque ipsum eum. Quia repudiandae et. Asperiores et est. Et modi minus cupiditate voluptas quia maxime delectus voluptatum.\n \rDignissimos numquam non nulla aut ea a quis. Et consequuntur porro ut dolorem iure est voluptas. Autem ullam mollitia perferendis mollitia sequi voluptatem numquam voluptatum.\n \rIllum magnam consequatur iste ut libero tenetur aut eos quibusdam. Aut molestiae ullam maiores. Sapiente est sed.",
sendNotification: null,
timestamp: "2019-06-26T01:41:43.197Z"
},
{
isPublic: false,
scheduledPost: false,
_id: "5d1f0a9289511160ddd681fa",
author: "Kari Ullrich",
title: "Iure ea est.",
content: "Molestiae magni nisi. Sit est sed aliquid corrupti perferendis natus. Est dolore totam. Quis excepturi id id ipsa. Accusantium aliquam facere laboriosam quo mollitia voluptatum.\n \rEum nulla ratione. Quia fuga ipsum fuga est temporibus eligendi aut. Aperiam alias architecto molestias aut beatae ullam harum. Magni in numquam dolores.\n \rIpsum quod itaque vero iste. Architecto vero ut quia. Voluptas modi provident et consequatur consequatur ex quia.",
sendNotification: null,
timestamp: "2019-06-22T19:29:40.080Z"
},
{
isPublic: false,
scheduledPost: true,
_id: "5d1f0a9289511160ddd681f9",
author: "Maida Bednar",
title: "Et dignissimos rem officiis non voluptas ea totam atque.",
content: "Totam dolorum quia dolor. Tempore molestiae error eos qui rerum ipsa nihil voluptas est. Aut et adipisci voluptatibus. Consectetur vel sit est voluptatum fuga qui minima sit recusandae.\n \rProvident dolorem ut. In rerum hic porro odio illo praesentium qui quis animi. Cupiditate est tenetur magnam commodi. Id est temporibus itaque corporis voluptatum omnis repellat eius. Deserunt tempora cum facilis. Nesciunt blanditiis voluptas mollitia.\n \rVoluptate vero omnis non nesciunt officia. Ipsa perferendis soluta. Sed unde labore autem quae amet deleniti consequatur. Molestias ducimus iusto a ipsa. Repellat vero accusamus impedit quo. Molestias nam inventore aut quaerat error odio eius enim velit.",
sendNotification: "2019-08-16T20:54:01.358Z",
timestamp: "2019-06-27T23:24:40.677Z"
},
{
isPublic: true,
scheduledPost: false,
_id: "5d1f0a9289511160ddd681f8",
author: "Emory Ortiz II",
title: "Ut doloribus quas impedit velit nostrum nemo facere nam.",
content: "Et et ab sapiente voluptatibus. Modi aperiam ad mollitia sunt minima architecto. Soluta aliquid vero. Est non eum esse. Numquam rerum quod. Itaque quasi numquam dolor impedit ut sit.\n \rQuo sapiente optio ea labore non dolor. Et nemo recusandae ea. Laboriosam magnam commodi excepturi quis soluta et. Molestias pariatur et ullam nesciunt ullam.\n \rCulpa enim vitae modi non totam sit eius beatae. Quia autem atque veniam et at neque. Reprehenderit possimus esse a qui nobis et laborum rerum.\n \rEt odio totam adipisci autem. Qui at quo aliquid ut rerum. Iste enim voluptatibus ipsam distinctio doloribus laborum neque. Qui quia quis sint repellendus.",
sendNotification: null,
timestamp: "2019-07-02T22:27:11.838Z"
}
],
showFrom: "2019-06-12T12:56:23.090Z",
showUntil: "2020-01-02T05:39:39.000Z",
startTime: "2019-06-26T12:56:23.090Z",
tagline: "Voluptates reiciendis eos tempora placeat dolores eum omnis voluptatibus molestiae.",
title: "et",
url: "https://www.mfa.org/summer-party",
ticketClasses: [
{
price: 171,
_id: "5d1f0a9289511160ddd68225",
available: 105,
capacity: 226,
endSale: "2020-01-02T05:39:39.957Z",
itemId: "5d1f0a9289511160ddd681fc",
name: "Magni vero voluptatem velit occaecati in fugit et dolor quas.",
startSale: "2019-06-26T12:56:23.090Z"
}
],
};

View File

@@ -0,0 +1,15 @@
import { StyleSheet } from 'react-native';
export default const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
title: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
});

View File

@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import { fetchEvents } from '../actions/events.js';
import { getEventsAsList } from '../selectors/events.js';
import Events from './Events.js';
const matchStateToProps = (state) => {
const events = getEventsAsList(state);
console.log('events:', events);
return { events };
};
const mapDispatchToProps = (dispatch) => ({
fetchEvents: () => dispatch(fetchEvents(dispatch)),
});
export default connect(matchStateToProps, mapDispatchToProps)(Events);

View File

@@ -20,15 +20,27 @@ export default class Events extends Component {
}
componentDidMount() {
this.props.fetchEvents();
}
_keyExtractor = (event, index) => `${event._id}_${index}`;
_renderEventListItem = ({ event }) => <EventListItem event={event} />;
render() {
const { events } = this.props;
return (
<View style={styles.container}>
<Text style={styles.title}>
Events
</Text>
{events.size > 0 && (
<FlatList
data={events}
keyExtractor={this._keyExtractor}
renderItem={this._renderEventListItem}
contentContainerStyle={styles.eventListContentContainer}
style={styles.eventList}
/>
)}
</View>
);
}
@@ -41,9 +53,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
title: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
eventListContentContainer: {
},
});

View File

@@ -0,0 +1,32 @@
import { connect } from 'react-redux';
import { fetchProfile, updateProfile } from '../actions/profile.js';
import { getNomDeBid, getProfile, isAllowedToBid } from '../selectors/profile.js';
import Profile from './Profile.js';
const matchStateToProps = (state) => {
const profile = getProfile(state);
return {
hasLinkedApple: profile.get('hasLinkedApple'),
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'),
};
};
const mapDispatchToProps = (dispatch) => ({
fetchProfile: () => dispatch(fetchProfile(dispatch)),
updateProfile: () => dispatch(updateProfile(dispatch)),
});
export default connect(matchStateToProps, mapDispatchToProps)(Profile);

View File

@@ -0,0 +1,13 @@
import { createSelector } from 'reselect';
const getState = (state) => state;
export const getActiveEventId = createSelector(
[getState],
(state) => state.get('activeEvent'),
);
export const hasActiveEvent = createSelector(
[getState],
(state) => !!state.get('activeEvent'),
);

View File

@@ -1,12 +1,9 @@
import { Map } from 'immutable';
import { createSelector } from 'reselect';
const getState = (state) => state;
import { getActiveEventId } from './activeEvent.js';
export const getActiveEvent = (state) => {
const eventId = state.get('activeEvent');
return state.getIn(['events', eventId], false)
};
const getState = (state) => state;
export const getEventById = (state, eventId) => state.getIn(['events', eventId], false);
@@ -15,7 +12,22 @@ export const getEvents = createSelector(
(state) => state.get('events') || new Map(),
);
export const getActiveEvent = createSelector(
[getActiveEventId, getEvents],
(eventId, eventsAsMap) => eventId ? eventsAsMap.get(eventId) : false,
);
export const getDefaultEvent = createSelector(
[getEvents],
(eventsAsMap) => eventsAsMap.first(),
);
export const getEventsAsList = createSelector(
[getEvents],
(eventsAsMap) => eventsAsMap.toList(),
);
export const hasMultipleEvents = createSelector(
[getEvents],
(eventsAsMap) => eventsAsMap.size > 1,
);

24
app/selectors/profile.js Normal file
View File

@@ -0,0 +1,24 @@
import { Map } from 'immutable';
import { createSelector } from 'reselect';
const getState = (state) => state;
export const getProfile = createSelector(
[getState],
(state) => state.get('profile'),
);
export const getNomDeBid = createSelector(
[getProfile],
(profile) => profile.get('nomDeBid'),
);
export const getProfileAvatarUrl = createSelector(
[getProfile],
(profile) => profile.get('avatar'),
);
export const isAllowedToBid = createSelector(
[getProfile],
(profile) => profile.get('isAllowedToBid'),
);