- implementing immutable.js
This commit is contained in:
40
app/App.js
40
app/App.js
@@ -7,46 +7,10 @@
|
||||
*/
|
||||
|
||||
import React, {Fragment} from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
View,
|
||||
Text,
|
||||
StatusBar,
|
||||
} from 'react-native';
|
||||
import { createAppContainer } from 'react-navigation';
|
||||
|
||||
import { Tabs } from './router.js';
|
||||
|
||||
const App = () => {
|
||||
return <Tabs />;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollView: {
|
||||
backgroundColor: Colors.lighter,
|
||||
},
|
||||
body: {
|
||||
backgroundColor: Colors.white,
|
||||
},
|
||||
sectionContainer: {
|
||||
marginTop: 32,
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
color: Colors.black,
|
||||
},
|
||||
sectionDescription: {
|
||||
marginTop: 8,
|
||||
fontSize: 18,
|
||||
fontWeight: '400',
|
||||
color: Colors.dark,
|
||||
},
|
||||
highlight: {
|
||||
fontWeight: '700',
|
||||
},
|
||||
});
|
||||
const App = createAppContainer(Tabs);
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,29 +1,82 @@
|
||||
import { List } from 'immutable';
|
||||
|
||||
import { getEndpointUrl } from '../api/index.js';
|
||||
|
||||
import Auction from '../domain/Auction.js';
|
||||
import Event from '../domain/Event.js';
|
||||
import Item from '../domain/Item.js';
|
||||
|
||||
import {
|
||||
AUCTIONS_UPDATED,
|
||||
BLOCK_UI,
|
||||
EVENTS_LOADED,
|
||||
GET_EVENTS,
|
||||
GET_ITEMS,
|
||||
ITEMS_LOADED,
|
||||
UNBLOCK_UI,
|
||||
} from '../constants/actionTypes.js';
|
||||
|
||||
import { API_ENDPOINTS } from '../constants/constants.js';
|
||||
|
||||
export const getEvents = () => (dispatch) => {
|
||||
return fetch(getEndpointUrl[API_ENDPOINTS.GET_EVENTS])
|
||||
dispatch(blockUI());
|
||||
fetch(getEndpointUr(API_ENDPOINTS.GET_EVENTS))
|
||||
.then(response => response.json())
|
||||
.then(payload => {
|
||||
dispatch({ type: EVENTS_LOADED, payload });
|
||||
.then((payload) => {
|
||||
const events = List(payload).map((i) => Event.fromJS(i));
|
||||
dispatch({ type: EVENTS_LOADED, payload: events });
|
||||
dispatch(unblockUI);
|
||||
});
|
||||
};
|
||||
|
||||
export const getItems = () => (dispatch, getState) => {
|
||||
const { activeEvent } = getState();
|
||||
let apiUrl = getEndpointUrl[API_ENDPOINTS.GET_ITEMS];
|
||||
apiUrl = apiUrl.replace(/:event_id/, activeEvent);
|
||||
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 => {
|
||||
const items = List(payload).map(i => Item.fromJS(i));
|
||||
dispatch({ type: ITEMS_LOADED, payload: items });
|
||||
dispatch(unblockUI());
|
||||
})
|
||||
.catch(err => console.error('[actions::getItems]', err));
|
||||
};
|
||||
|
||||
export const getStatus = () => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const activeEvent = state.get('activeEvent');
|
||||
|
||||
let apiUrl = getEndpointUrl(API_ENDPOINTS.GET_STATUS);
|
||||
apiUrl = apiUrl.replace(/:event_id$/, '');
|
||||
if (activeEvent) {
|
||||
apiUrl = `${apiUrl}${activeEvent}`;
|
||||
}
|
||||
|
||||
dispatch(blockUI());
|
||||
|
||||
return fetch(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(payload => {
|
||||
dispatch({ type: ITEMS_LOADED, payload });
|
||||
});
|
||||
const auctions = List(payload).map(i => Auction.fromJS(i));
|
||||
dispatch(unblockUI());
|
||||
dispatch({ type: AUCTIONS_UPDATED, payload: auctions });
|
||||
})
|
||||
.catch(err => console.error('[actions::getStatus]', err));
|
||||
};
|
||||
|
||||
export const blockUI = () => ({
|
||||
type: BLOCK_UI,
|
||||
});
|
||||
|
||||
export const unblockUI = () => ({
|
||||
type: UNBLOCK_UI,
|
||||
});
|
||||
|
||||
@@ -3,9 +3,11 @@ const apiUrl = 'http://localhost:3001';
|
||||
const endpoints = {
|
||||
// Events and Items
|
||||
GET_EVENTS: '/events',
|
||||
GET_ITEMS: '/items?eventId=:event_id',
|
||||
// GET_ITEMS: '/items?eventId=:event_id',
|
||||
GET_ITEMS: '/items',
|
||||
|
||||
// Auction Interactions
|
||||
// GET_STATUS: '/auction/:event_id',
|
||||
GET_STATUS: '/auction',
|
||||
PLACE_BID: '/bids/:item_id',
|
||||
PURCHASE_ITEM: '/sales',
|
||||
@@ -30,8 +32,8 @@ const cacheBuster = () => {
|
||||
|
||||
export const getEndpointUrl = (endpoint) => {
|
||||
if (!endpoints[endpoint]) {
|
||||
return throw new Error('Invalid API endpoint specified');
|
||||
throw new Error('Invalid API endpoint specified');
|
||||
}
|
||||
|
||||
return `${apiUrl}${endpoints[endpoint]}${cacheBuster()}`;
|
||||
return `${apiUrl}${endpoints[endpoint]}`; //`${cacheBuster()}`;
|
||||
};
|
||||
|
||||
@@ -17,8 +17,15 @@ const AuctionPriceAndBidCount = ({ bidCount, currentPrice }) => {
|
||||
};
|
||||
|
||||
AuctionPriceAndBidCount.propTypes = {
|
||||
itemId: PropTypes.string.isRequired,
|
||||
bidCount: PropTypes.number.isRequired,
|
||||
currentPrice: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
currentPriceAndBidCount: {
|
||||
color: '#000',
|
||||
},
|
||||
});
|
||||
|
||||
export default AuctionPriceAndBidCount;
|
||||
|
||||
@@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
} from 'react-native';
|
||||
|
||||
const BidStatus = ({ bidCount, currentPrice, isBidding, isWinning }) => {
|
||||
const BidStatus = ({ isBidding, isWinning }) => {
|
||||
if (!isBidding) {
|
||||
return null;
|
||||
}
|
||||
@@ -17,16 +17,16 @@ const BidStatus = ({ bidCount, currentPrice, isBidding, isWinning }) => {
|
||||
|
||||
return (
|
||||
<Text style={statusBarStyle} numberOfLines={1}>
|
||||
{`${currentPrice} (${bidCount} bids)`}
|
||||
{isWinning && `Oh no! You have been outbid!`}
|
||||
{!isWinning && isBidding && `You have the winning bid! (for now...)`}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
BidStatus.propTypes = {
|
||||
bidCount: PropTypes.number.isRequired,
|
||||
currentPrice: PropTypes.number.isRequired,
|
||||
isBidding: PropTypes.bool.isRequired,
|
||||
isWinning: PropTypes.bool.isRequired,
|
||||
itemId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
42
app/components/Auction/FilterBar.js
Normal file
42
app/components/Auction/FilterBar.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
const FilterBar = ({ changeFilterer, changeViewMode, filterMode, viewMode }) => (
|
||||
<View style={styles.filterBar}>
|
||||
<Text style={styles.filter}>Filter</Text>
|
||||
<Text style={styles.view}>View</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
FilterBar.propTypes = {
|
||||
changeFilterer: PropTypes.func.isRequired,
|
||||
changeViewMode: PropTypes.func.isRequired,
|
||||
filterMode: PropTypes.string,
|
||||
viewMode: PropTypes.string,
|
||||
};
|
||||
|
||||
FilterBar.defaultProps = {
|
||||
filterMode: null,
|
||||
viewMode: null,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
filterBar: {
|
||||
backgroundColor: '#0F0',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
filter: {
|
||||
flex: 2,
|
||||
},
|
||||
view: {
|
||||
flex: 2,
|
||||
},
|
||||
});
|
||||
|
||||
export default FilterBar;
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
@@ -7,6 +9,8 @@ import {
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import GallerySwiper from 'react-native-gallery-swiper';
|
||||
|
||||
import AuctionPriceAndBidCount from '../../containers/Auction/AuctionPriceAndBidCount.js';
|
||||
import BidStatus from '../../containers/Auction/BidStatus.js';
|
||||
|
||||
@@ -25,21 +29,34 @@ export default class ItemRow extends Component {
|
||||
url: PropTypes.string,
|
||||
}),
|
||||
),
|
||||
start: PropTytpes.string.isRequired,
|
||||
startPrice: PropTypes.number,
|
||||
start: PropTypes.string.isRequired,
|
||||
startingPrice: PropTypes.number.isRequired,
|
||||
subtitle: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
description: null,
|
||||
donor: null,
|
||||
images: null,
|
||||
subtitle: null,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
_getBidTime = () => {
|
||||
const { end, start } = this.props;
|
||||
return getAuctionTime({ end, start });
|
||||
}
|
||||
|
||||
_viewItemDetail = () => {
|
||||
const { id } = this.props.details;
|
||||
const { _id: id } = this.props.details;
|
||||
this.props.navigation.navigate('Item', { id });
|
||||
}
|
||||
|
||||
@@ -48,9 +65,10 @@ export default class ItemRow extends Component {
|
||||
description,
|
||||
donor,
|
||||
end,
|
||||
id,
|
||||
images,
|
||||
start,
|
||||
startPrice,
|
||||
startingPrice,
|
||||
subtitle,
|
||||
title,
|
||||
type,
|
||||
@@ -59,13 +77,18 @@ export default class ItemRow extends Component {
|
||||
return(
|
||||
<TouchableOpacity onPress={this._viewItemDetail}>
|
||||
<View style={styles.rowContainer}>
|
||||
<Image
|
||||
source={{uri: images[0].url}}
|
||||
style={styles.image}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
{images !== null && images.length > 0 && (
|
||||
<GallerySwiper
|
||||
enableScale={false}
|
||||
images={images}
|
||||
initialNumToRender={2}
|
||||
resizeMode="cover"
|
||||
sensitiveScroll={false}
|
||||
style={{height: 280}}
|
||||
/>
|
||||
)}
|
||||
<View style={styles.rowText}>
|
||||
{type === ITEM_TYPES.AUCTION && <BidStatus id={item.id} />}
|
||||
{type === ITEM_TYPES.AUCTION && <BidStatus itemId={id} />}
|
||||
<Text style={styles.title} numberOfLines={2} ellipsizeMode={'tail'}>
|
||||
{title}
|
||||
</Text>
|
||||
@@ -78,10 +101,10 @@ export default class ItemRow extends Component {
|
||||
</Text>
|
||||
)}
|
||||
{type === ITEM_TYPES.AUCTION ? (
|
||||
<AuctionPriceAndBidCount id={item.id} />
|
||||
<AuctionPriceAndBidCount itemId={id} />
|
||||
) : (
|
||||
<Text style={styles.price} numberOfLines={1} ellipsizeMode={'tail'}>
|
||||
{formatPrice(startPrice)}
|
||||
{formatPrice(startingPrice)}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.timeline} numberOfLines={1}>
|
||||
@@ -126,14 +149,17 @@ const styles = StyleSheet.create({
|
||||
rowContainer: {
|
||||
backgroundColor: '#FFF',
|
||||
borderRadius: 4,
|
||||
flexDirection: 'row',
|
||||
height: 100,
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
marginRight: 10,
|
||||
marginLeft: 10,
|
||||
marginTop: 10,
|
||||
padding: 10,
|
||||
shadowColor: '#CCC',
|
||||
shadowOffset:{ width: 1, height: 1, },
|
||||
shadowOffset: {
|
||||
width: 1,
|
||||
height: 1
|
||||
},
|
||||
shadowOpacity: 1.0,
|
||||
shadowRadius: 1,
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ export const ITEMS_LOADED = 'ITEMS_LOADED';
|
||||
export const ITEMS_LOAD_FAILED = 'ITEMS_LOAD_FAILED';
|
||||
|
||||
export const UPDATE_AUCTIONS = 'UPDATE_AUCTIONS';
|
||||
export const AUCTIONS_UPDATED = 'AUCTIONS_UPDATED';
|
||||
|
||||
export const SET_TICKET_PURCHASE_FLOW = 'SET_TICKET_PURCHASE_FLOW';
|
||||
|
||||
@@ -34,6 +35,7 @@ export const LINK_FACEBOOK_SUCCESS = 'LINK_FACEBOOK_SUCCESS';
|
||||
export const LINK_GOOGLE_FAILURE = 'LINK_GOOGLE_FAILURE';
|
||||
export const LINK_GOOGLE_SUCCESS = 'LINK_GOOGLE_SUCCESS';
|
||||
|
||||
export const SET_PROFILE = 'SET_PROFILE';
|
||||
export const UPDATE_PROFILE = 'UPDATE_PROFILE';
|
||||
|
||||
export const SET_NOM_DE_BID = 'SET_NOM_DE_BID';
|
||||
@@ -41,3 +43,9 @@ export const SET_PASSWORD = 'SET_PASSWORD';
|
||||
|
||||
export const ADD_PAYMENT_DATA = 'ADD_PAYMENT_DATA';
|
||||
export const DO_CHECKOUT = 'DO_CHECKOUT';
|
||||
|
||||
export const BLOCK_UI = 'BLOCK_UI';
|
||||
export const UNBLOCK_UI = 'UNBLOCK_UI';
|
||||
|
||||
export const SET_ACTIVE_EVENT = 'SET_ACTIVE_EVENT';
|
||||
export const UNSET_ACTIVE_EVENT = 'UNSET_ACTIVE_EVENT';
|
||||
|
||||
18
app/containers/Auction.js
Normal file
18
app/containers/Auction.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getItems, getStatus } from '../actions/index.js';
|
||||
|
||||
import { getAuctionItemsAsList } from '../selectors/items.js';
|
||||
|
||||
import Auction from '../screens/Auction.js';
|
||||
|
||||
const matchStateToProps = (state) => ({
|
||||
items: getAuctionItemsAsList(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
fetchItems: () => dispatch(getItems(dispatch)),
|
||||
fetchStatus: () => dispatch(getStatus(dispatch)),
|
||||
});
|
||||
|
||||
export default connect(matchStateToProps, mapDispatchToProps)(Auction);
|
||||
@@ -1,13 +1,15 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getItemBidCount, getItemPrice } from '../../selectors/auctions.js';
|
||||
|
||||
import AuctionPriceAndBidCount from '../../components/Auction/AuctionPriceAndBidCount.js';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const { bidCount, currentPrice } = getAuctionItemStatus(state, ownProps.id);
|
||||
const { itemId } = ownProps;
|
||||
|
||||
return {
|
||||
bidCount,
|
||||
currentPrice,
|
||||
bidCount: getItemBidCount(state, itemId),
|
||||
currentPrice: getItemPrice(state, itemId),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { isBiddingItem, isWinningItem } from '../../selectors/auctions.js';
|
||||
|
||||
import AuctionPriceAndBidCount from '../../components/Auction/BidStatus.js';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {
|
||||
bidCount,
|
||||
currentPrice,
|
||||
isBidding,
|
||||
isWinning,
|
||||
} = getAuctionItemStatus(state, ownProps.id);
|
||||
const { itemId } = ownProps;
|
||||
|
||||
return {
|
||||
bidCount,
|
||||
currentPrice,
|
||||
isBidding,
|
||||
isWinning,
|
||||
isBidding: isBiddingItem(state, itemId),
|
||||
isWinning: isWinningItem(state, itemId),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
16
app/domain/Auction.js
Normal file
16
app/domain/Auction.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Record } from 'immutable';
|
||||
|
||||
export default class Auction extends Record({
|
||||
id: null,
|
||||
bidCount: 0,
|
||||
isBidding: false,
|
||||
isWinning: false,
|
||||
itemPrice: 0,
|
||||
}) {}
|
||||
|
||||
Auction.fromJS = (data = {}) => {
|
||||
return new Auction({
|
||||
id: data._id,
|
||||
...data,
|
||||
});
|
||||
};
|
||||
39
app/domain/Event.js
Normal file
39
app/domain/Event.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { List, Record } from 'immutable';
|
||||
|
||||
import Post from './Post.js';
|
||||
import TicketClass from './TicketClass.js';
|
||||
|
||||
export default class Event extends Record({
|
||||
id: null,
|
||||
isTicketed: false,
|
||||
requireLoginToSeeAuction: false,
|
||||
description: null,
|
||||
endTime: null,
|
||||
images: new List(),
|
||||
posts: new List(),
|
||||
showFrom: null,
|
||||
showUntil: null,
|
||||
startTime: null,
|
||||
tagline: null,
|
||||
title: null,
|
||||
url: null,
|
||||
ticketClasses: new List(),
|
||||
}) {
|
||||
get isSoldOut() {
|
||||
if (this.isTicketed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.ticketClasses.find(t => t.available > 0) || false;
|
||||
}
|
||||
}
|
||||
|
||||
Event.fromJS = (data = {}) => {
|
||||
return new Event({
|
||||
id: data._id,
|
||||
...data,
|
||||
images: new List(data.images),
|
||||
posts: new List(data.posts.map(p => Post.fromJS(p))),
|
||||
ticketClasses: new List(data.ticketClasses.map(t => TicketClass.fromJS(t))),
|
||||
});
|
||||
};
|
||||
43
app/domain/Item.js
Normal file
43
app/domain/Item.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { List, Record } from 'immutable';
|
||||
|
||||
export default class Item extends Record({
|
||||
bidCount: 0,
|
||||
bidIncrement: 10,
|
||||
catalogNumber: null,
|
||||
currentPrice: 0,
|
||||
description: null,
|
||||
donor: null,
|
||||
end: null,
|
||||
estimatedValue: null,
|
||||
eventId: null,
|
||||
hideAfterEnd: false,
|
||||
hideBeforeStart: false,
|
||||
id: null,
|
||||
images: new List(),
|
||||
isShippable: false,
|
||||
notifyOnAvailable: false,
|
||||
quantityAvailable: 1,
|
||||
soldCount: 0,
|
||||
start: null,
|
||||
startingPrice: null,
|
||||
subtitle: null,
|
||||
title: null,
|
||||
type: null,
|
||||
shippingCost: 0,
|
||||
}) {
|
||||
get isSoldOut() {
|
||||
return this.quantityAvailable > this.soldCount;
|
||||
}
|
||||
|
||||
get totalWithShipping() {
|
||||
return this.currentPrice + this.shippingCost;
|
||||
}
|
||||
}
|
||||
|
||||
Item.fromJS = (data = {}) => {
|
||||
return new Item({
|
||||
id: data._id,
|
||||
...data,
|
||||
images: List(data.images),
|
||||
});
|
||||
};
|
||||
20
app/domain/Post.js
Normal file
20
app/domain/Post.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Record } from 'immutable';
|
||||
|
||||
export default class Post extends Record({
|
||||
author: null,
|
||||
content: null,
|
||||
id: null,
|
||||
isPublic: false,
|
||||
scheduledPost: false,
|
||||
sendNotification: false,
|
||||
timestamp: null,
|
||||
title: null,
|
||||
}) {};
|
||||
|
||||
|
||||
Post.fromJS = (data = {}) => {
|
||||
return new TicketClass({
|
||||
id: data._id,
|
||||
...data,
|
||||
});
|
||||
};
|
||||
39
app/domain/Profile.js
Normal file
39
app/domain/Profile.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { List, Record } from 'immutable';
|
||||
|
||||
export default class Profile extends Record({
|
||||
addresses: new List(),
|
||||
avatar: null,
|
||||
email: null,
|
||||
firstName: null,
|
||||
generatedNomDeBid: false,
|
||||
hasLinkedApple: false,
|
||||
hasLinkedFacebook: false,
|
||||
hasLinkedGoogle: false,
|
||||
hasLocalAccount: false,
|
||||
id: null,
|
||||
isAllowedToBid: false,
|
||||
isOrganizationEmployee: false,
|
||||
isVerified: false,
|
||||
lastName: null,
|
||||
nomDeBid: null,
|
||||
organizationIdentifier: null,
|
||||
paymentToken: null,
|
||||
phones: new List(),
|
||||
}) {
|
||||
get canBid() {
|
||||
return this.isAllowedToBid && this.paymentToken !== null;
|
||||
}
|
||||
|
||||
get fullName() {
|
||||
return `${this.firstName} ${this.lastName}`;
|
||||
}
|
||||
}
|
||||
|
||||
Profile.fromJS = (data = {}) => {
|
||||
return new Profile({
|
||||
id: data._id,
|
||||
...data,
|
||||
addresses: new List(data.addresses),
|
||||
phones: new List(data.phones),
|
||||
});
|
||||
};
|
||||
27
app/domain/TicketClass.js
Normal file
27
app/domain/TicketClass.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { List, Record } from 'immutable';
|
||||
|
||||
export default class TicketClass extends Record({
|
||||
available: 0,
|
||||
capacity: 0,
|
||||
endSale: null,
|
||||
id: null,
|
||||
itemId: null,
|
||||
name: null,
|
||||
price: 0,
|
||||
startSale: null,
|
||||
}) {
|
||||
get isAlmostGone() {
|
||||
return this.available < (this.capacity * 0.20);
|
||||
}
|
||||
|
||||
get isSoldOut() {
|
||||
return this.available === 0;
|
||||
}
|
||||
}
|
||||
|
||||
TicketClass.fromJS = (data = {}) => {
|
||||
return new TicketClass({
|
||||
id: data._id,
|
||||
...data,
|
||||
});
|
||||
};
|
||||
12
app/reducers/activeEvent.js
Normal file
12
app/reducers/activeEvent.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { SET_ACTIVE_EVENT, UNSET_ACTIVE_EVENT } from '../constants/actionTypes.js';
|
||||
|
||||
export const activeEvent = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case SET_ACTIVE_EVENT:
|
||||
return action.payload;
|
||||
case UNSET_ACTIVE_EVENT:
|
||||
return null;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
18
app/reducers/auctions.js
Normal file
18
app/reducers/auctions.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Map } from 'immutable';
|
||||
|
||||
import { AUCTIONS_UPDATED, UPDATE_AUCTIONS } from '../constants/actionTypes.js';
|
||||
|
||||
export const auctions = (state = new Map(), action) => {
|
||||
switch (action.type) {
|
||||
case AUCTIONS_UPDATED:
|
||||
return state.merge(
|
||||
action.payload.toMap().mapEntries((entry) => {
|
||||
const [, item] = entry;
|
||||
return [`${item.id}`, item];
|
||||
}),
|
||||
);
|
||||
case UPDATE_AUCTIONS:
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
12
app/reducers/blockUI.js
Normal file
12
app/reducers/blockUI.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BLOCK_UI, UNBLOCK_UI } from '../constants/actionTypes.js';
|
||||
|
||||
export const blockUI = (state = false, action) => {
|
||||
switch (action.type) {
|
||||
case BLOCK_UI:
|
||||
return true;
|
||||
case UNBLOCK_UI:
|
||||
return false;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Map } from 'immutable';
|
||||
|
||||
import { EVENTS_LOADED, GET_EVENTS } from '../constants/actionTypes.js';
|
||||
|
||||
export const events = (state = {}, action) => {
|
||||
export const events = (state = new Map(), action) => {
|
||||
switch (action.type) {
|
||||
case GET_EVENTS:
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
case EVENTS_LOADED:
|
||||
return Object.assign({}, state, {
|
||||
events: action.payload,
|
||||
isFetching: false,
|
||||
});
|
||||
return state.merge(
|
||||
action.payload.toMap().mapEntries((entry) => {
|
||||
const [, event] = entry;
|
||||
return [`${event.id}`, event];
|
||||
}),
|
||||
);
|
||||
case GET_EVENTS:
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
|
||||
import { activeEvent } from './activeEvent.js';
|
||||
import { auctions } from './auctions.js';
|
||||
import { blockUI } from './blockUI.js';
|
||||
import { events } from './events.js';
|
||||
import { items } from './items.js';
|
||||
|
||||
const initialState = {
|
||||
auction: {},
|
||||
cart: [],
|
||||
isFetching: false,
|
||||
items: [],
|
||||
profile: {},
|
||||
};
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
activeEvent,
|
||||
auctions,
|
||||
blockUI,
|
||||
events,
|
||||
items,
|
||||
});
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { ITEMS_LOADED, GET_ITEMS } from '../constants/actionTypes.js';
|
||||
import { Map } from 'immutable';
|
||||
|
||||
export const items = (state = {}, action) => {
|
||||
import {
|
||||
GET_ITEMS,
|
||||
ITEMS_LOADED,
|
||||
} from '../constants/actionTypes.js';
|
||||
|
||||
export const items = (state = new Map(), action) => {
|
||||
switch (action.type) {
|
||||
case GET_ITEMS:
|
||||
return Object.assign({}, state, {
|
||||
isFetching: true,
|
||||
});
|
||||
case ITEMS_LOADED:
|
||||
return Object.assign({}, state, {
|
||||
items: action.payload,
|
||||
isFetching: false,
|
||||
const mapped = action.payload.toMap().mapEntries((entry) => {
|
||||
const [, item] = entry;
|
||||
return [`${item.id}`, item];
|
||||
});
|
||||
return state.merge(mapped);
|
||||
case GET_ITEMS:
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
17
app/reducers/profile.js
Normal file
17
app/reducers/profile.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Map } from 'immutable';
|
||||
|
||||
import {
|
||||
SET_PROFILE,
|
||||
UPDATE_PROFILE,
|
||||
} from '../constants/actionTypes.js';
|
||||
|
||||
export const profile = (state = new Map(), action) => {
|
||||
switch (action.type) {
|
||||
case SET_PROFILE:
|
||||
return action.payload;
|
||||
case UPDATE_PROFILE:
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { Dimensions, Platform } from 'react-native';
|
||||
import { createBottomTabNavigator, createStackNavigator } from 'react-navigation';
|
||||
import { Icon } from 'react-native-elements';
|
||||
|
||||
import Auction from './screens/Auction.js';
|
||||
import Auction from './containers/Auction.js';
|
||||
import Checkout from './screens/Checkout.js';
|
||||
import Event from './screens/Event.js';
|
||||
import Events from './screens/Events.js';
|
||||
@@ -19,28 +19,28 @@ export const Tabs = createBottomTabNavigator({
|
||||
screen: Event,
|
||||
navigationOptions: {
|
||||
tabBarLabel: 'Event',
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="black-tie" type="fontawesome" size={28} color={tintColor} />,
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="black-tie" type="font-awesome" size={28} color={tintColor} />,
|
||||
},
|
||||
},
|
||||
'Auction': {
|
||||
screen: Auction,
|
||||
navigationOptions: {
|
||||
tabBarLabel: 'Silent Auction',
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="gavel" type="fontawesome" size={28} color={tintColor} />,
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="gavel" type="font-awesome" size={28} color={tintColor} />,
|
||||
},
|
||||
},
|
||||
'Bazaar': {
|
||||
screen: Marketplace,
|
||||
navigationOptions: {
|
||||
tabBarLabel: 'Bazaar',
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="shopping-store" type="fontisto" size={28} color={tintColor} />,
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="store" type="fontisto" size={28} color={tintColor} />,
|
||||
},
|
||||
},
|
||||
'Profile': {
|
||||
screen: Profile,
|
||||
navigationOptions: {
|
||||
tabBarLabel: 'Profile',
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="ios-person-outline" type="ionicon" size={28} color={tintColor} />,
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="ios-person" type="font-awesome" size={28} color={tintColor} />,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
|
||||
import { SORT_MODES, VIEW_MODES } from '../constants/constants.js';
|
||||
|
||||
import FilterBar from '../components/Auction/FilterBar.js';
|
||||
import GridItem from '../components/Item/Grid.js';
|
||||
import ListItem from '../components/Item/List.js';
|
||||
|
||||
@@ -17,14 +18,16 @@ export default class Auction extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
changeFilter: PropTypes.func,
|
||||
items: PropTypes.array.isRequired,
|
||||
fetchItems: PropTypes.func.isRequired,
|
||||
fetchStatus: PropTypes.func.isRequired,
|
||||
items: PropTypes.array,
|
||||
};
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
changeFilter: () => { console.log('Change Filter Default Prop', arguments); },
|
||||
header: null,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,6 +43,11 @@ export default class Auction extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchStatus();
|
||||
this.props.fetchItems();
|
||||
}
|
||||
|
||||
changeFilter(filter) {
|
||||
this.props.changeFilter('auction', filter);
|
||||
}
|
||||
@@ -48,18 +56,20 @@ export default class Auction extends Component {
|
||||
this.setState({ view: mode });
|
||||
}
|
||||
|
||||
_keyExtractor = (item, index) => item.id;
|
||||
_keyExtractor = (item, index) => `${item._id}_${index}`;
|
||||
|
||||
_renderItem = (view) => ({ item }) => {
|
||||
console.log('_renderItem', item);
|
||||
if (view === VIEW_MODES.GRID) {
|
||||
return <GridItem details={item} />;
|
||||
return <GridItem {...item} />;
|
||||
}
|
||||
|
||||
return <ListItem details={item} />;
|
||||
return <ListItem {...item} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { items, view } = this.state;
|
||||
const { items } = this.props;
|
||||
const { sort, view } = this.state;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@@ -67,11 +77,15 @@ export default class Auction extends Component {
|
||||
changeFilterer={this.changeFilter}
|
||||
changeViewMode={this.changeViewMode}
|
||||
/>
|
||||
<FlatList
|
||||
data={items}
|
||||
keyExtractor={this._keyExtractor}
|
||||
renderItem={this.renderItem(view)}
|
||||
/>
|
||||
{items.size > 0 && (
|
||||
<FlatList
|
||||
data={items}
|
||||
keyExtractor={this._keyExtractor}
|
||||
renderItem={this._renderItem(view)}
|
||||
contentContainerStyle={styles.itemListContentContainer}
|
||||
style={styles.itemList}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -84,9 +98,11 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
}
|
||||
itemList: {
|
||||
width: '100%',
|
||||
},
|
||||
itemListContentContainer: {
|
||||
alignItems: 'stretch',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -6,6 +8,21 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
export default class Events extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
events: PropTypes.array.isRequired,
|
||||
fetchEvents: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
||||
13
app/selectors/auctions.js
Normal file
13
app/selectors/auctions.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
//import { getItemsIdsWithNoBids, getMyBidItemIds, getMyWinningItemIds } from './auctions.js';
|
||||
|
||||
const getState = (state) => state;
|
||||
|
||||
export const getItemBidCount = (state, itemId) => state.getIn(['auctions', itemId, 'bidCount'], 0);
|
||||
|
||||
export const getItemPrice = (state, itemId) => state.getIn(['auctions', itemId, 'currentPrice'], 0);
|
||||
|
||||
export const isBiddingItem = (state, itemId) => state.getIn(['auctions', itemId, 'isBidding'], false);
|
||||
|
||||
export const isWinningItem = (state, itemId) => state.getIn(['auctions', itemId, 'isWinning'], false);
|
||||
42
app/selectors/items.js
Normal file
42
app/selectors/items.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
//import { getItemsIdsWithNoBids, getMyBidItemIds, getMyWinningItemIds } from './auctions.js';
|
||||
|
||||
const getState = (state) => state;
|
||||
|
||||
export const getItem = (state, itemId) => state.getIn(['items', itemId], false);
|
||||
|
||||
export const getItems = createSelector(
|
||||
[getState],
|
||||
(state) => state.get('items') || new Map(),
|
||||
);
|
||||
|
||||
export const getItemsAsList = createSelector(
|
||||
[getItems],
|
||||
(itemsAsMap) => itemsAsMap.toList(),
|
||||
);
|
||||
|
||||
export const getAuctionItems = createSelector(
|
||||
[getState],
|
||||
(state) => state.get('items').filter(i => i.type === 'auction') || new Map(),
|
||||
);
|
||||
|
||||
export const getAuctionItemsAsList = createSelector(
|
||||
[getAuctionItems],
|
||||
(auctionItemsAsMap) => auctionItemsAsMap.toList(),
|
||||
);
|
||||
|
||||
export const getTicketItems = createSelector(
|
||||
[getState],
|
||||
(state) => state.get('items').filter(i => i.type === 'ticket') || new Map(),
|
||||
);
|
||||
|
||||
export const getTicketItemsAsList = createSelector(
|
||||
[getTicketItems],
|
||||
(ticketItemsAsMap) => ticketItemsAsMap.toList(),
|
||||
);
|
||||
|
||||
export const getAuctionItemsWithNoBids = createSelector(
|
||||
[getAuctionItems],
|
||||
(auctionItemsAsMap) => auctionItemsAsMap.filter(i => i.bidCount),
|
||||
);
|
||||
@@ -1,9 +1,14 @@
|
||||
import { Map } from 'immutable';
|
||||
import { applyMiddleware, compose, createStore } from 'redux';
|
||||
import { composeWithDevTools } from 'remote-redux-devtools';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import rootReducer from '../reducers/index.js';
|
||||
|
||||
const composeEnhancers = composeWithDevTools({ port: 8000, realtime: true, suppressConnectErrors: false });
|
||||
|
||||
export const store = createStore(
|
||||
rootReducer,
|
||||
compose(applyMiddleware(thunk)),
|
||||
Map(),
|
||||
composeEnhancers(applyMiddleware(thunk)),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user