Merge branch 'master' of honey.fitz.guru:eventment-app

# Conflicts:
#	app/components/Login/LocalLogin.js
#	app/screens/Register.js
This commit is contained in:
Mike Fitzpatrick
2019-08-07 10:23:40 -04:00
102 changed files with 2111 additions and 2054 deletions

View File

@@ -1,4 +1,81 @@
module.exports = { module.exports = {
root: true, root: true,
extends: '@react-native-community', extends: '@react-native-community',
env: {
mocha: true,
browser: true,
},
// parser: 'babel-eslint',
// plugins: ['babel', 'immutablejs'],
plugins: ['immutablejs'],
rules: {
'comma-dangle': 'off',
'func-call-spacing': 'off',
'import/prefer-default-export': 'off',
'max-len': ['error', 150, { ignoreComments: true, ignoreTemplateLiterals: true }],
'no-confusing-arrow': 'off',
'no-mixed-operators': 'off',
'no-restricted-properties': [
2,
{
object: 'describe',
property: 'only',
message:
'Please remove any instance of .only from unit tests. If .only is required outside of a unit test, feel free to add an eslint override to ignore this instance of this error.',
},
{
object: 'it',
property: 'only',
message:
'Please remove any instance of .only from unit tests. If .only is required outside of a unit test, feel free to add an eslint override to ignore this instance of this error.',
},
{
object: 'context',
property: 'only',
message:
'Please remove any instance of .only from unit tests. If .only is required outside of a unit test, feel free to add an eslint override to ignore this instance of this error.',
},
{
object: 'tape',
property: 'only',
message:
'Please remove any instance of .only from unit tests. If .only is required outside of a unit test, feel free to add an eslint override to ignore this instance of this error.',
},
{
object: 'test',
property: 'only',
message:
'Please remove any instance of .only from unit tests. If .only is required outside of a unit test, feel free to add an eslint override to ignore this instance of this error.',
},
{
object: 'fixture',
property: 'only',
message:
'Please remove any instance of .only from unit tests. If .only is required outside of a unit test, feel free to add an eslint override to ignore this instance of this error.',
},
],
'no-spaced-func': 'off',
'react/jsx-closing-bracket-location': 'off',
'react/jsx-first-prop-new-line': 'off',
'react/jsx-no-literals': ['error', { noStrings: true }],
'react/jsx-no-target-blank': 'off',
'react/no-danger': 'off',
'space-before-function-paren': 'off',
indent: 'off',
'indent-legacy': 'off',
'immutablejs/no-native-map-set': 'error',
'no-invalid-this': 'off',
// 'babel/no-invalid-this': 'error',
},
overrides: [
{
files: ['lib/fixtures/**/*.js', '**/testHelper.js'],
rules: {
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
},
globals: {
expect: false,
},
},
],
}; };

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"arrowParens": "always",
"jsxBracketSameLine": false,
"singleQuote": true,
"tabWidth": 4,
"printWidth": 100,
"trailingComma": "all"
}

View File

@@ -10,5 +10,5 @@ import App from '../App';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
it('renders correctly', () => { it('renders correctly', () => {
renderer.create(<App />); renderer.create(<App />);
}); });

View File

@@ -1,12 +1,3 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow
*/
import React from 'react';
import { createAppContainer } from 'react-navigation'; import { createAppContainer } from 'react-navigation';
import { Tabs } from './router.js'; import { Tabs } from './router.js';

View File

@@ -0,0 +1,6 @@
import { SET_ACTIVE_EVENT } from '../constants/actionTypes.js';
export const setActiveEvent = (eventId) => ({
type: SET_ACTIVE_EVENT,
payload: eventId,
});

View File

@@ -1,52 +1,54 @@
import { blockUI, unblockUI } from './index.js'; import { placeBid as placeBidApi } from '../api/bid.js';
import { import {
BID_FAILURE, BID_FAILURE,
BID_SUCCESS, BID_SUCCESS,
PLACE_BID, PLACE_BID,
SET_AUCTION_FILTER, SET_AUCTION_FILTER,
SET_AUCTION_VIEW_MODE, SET_AUCTION_VIEW_MODE,
} from '../constants/actionTypes.js'; } from '../constants/actionTypes.js';
import { getActiveEventId } from '../selectors/activeEvent.js';
import { getAuthToken } from '../selectors/auth.js';
const placeBidFailure = (bid, dispatch) => { const placeBidFailure = (payload) => ({
dispatch({ type: BID_FAILURE, bid }); type: BID_FAILURE,
dispatch(unblockUI); payload,
});
const placeBidSuccess = (payload) => ({
type: BID_SUCCESS,
payload,
});
const handleBidFailure = (error) => (dispatch) => {
console.error('[postBid]', error);
dispatch(placeBidFailure(error));
}; };
const placeBidSuccess = (bid, dispatch) => { const handleBidSuccess = (result) => (dispatch) => {
dispatch({ type: BID_SUCCESS, bid }); dispatch(placeBidSuccess(result));
dispatch(unblockUI);
}; };
const startPlacingBid = (itemId) => ({
type: PLACE_BID,
payload: itemId,
});
export const changeFilterMode = (payload) => ({ export const changeFilterMode = (payload) => ({
type: SET_AUCTION_FILTER, type: SET_AUCTION_FILTER,
payload, payload,
}); });
export const changeViewMode = (payload) => ({ export const changeViewMode = (payload) => ({
type: SET_AUCTION_VIEW_MODE, type: SET_AUCTION_VIEW_MODE,
payload, payload,
}); });
export const placeBid = (payload) => ({ export const placeBid = ({ bidAmount, itemId, maxAmount }) => (dispatch, getState) => {
type: PLACE_BID, const authToken = getAuthToken(getState());
payload, const eventId = getActiveEventId(getState());
});
export const postBid = () => (dispatch, getState) => { dispatch(startPlacingBid(itemId));
const state = getState(); placeBidApi({ bidAmount, eventId, itemId, maxAmount }, authToken)
const activeEvent = state.get('activeEvent'); .then((payload) => dispatch(handleBidSuccess(payload)))
.catch((err) => dispatch(handleBidFailure(err)));
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

@@ -1,39 +1,27 @@
import { List } from 'immutable'; import { List } from 'immutable';
import { getEndpointUrl } from '../api/index.js'; import { fetchAuctionStatus as fetchActionStatusApi } from '../api/actionStatus.js';
import { AUCTIONS_UPDATED } from '../constants/actionTypes.js';
import { import { getActiveEventId } from '../selectors/activeEvent.js';
AUCTIONS_UPDATED, import { getAuthToken } from '../selectors/auth.js';
ITEMS_LOADED,
} from '../constants/actionTypes.js';
import { blockUI, unblockUI } from './index.js';
import { API_ENDPOINTS } from '../constants/constants.js';
import Auction from '../domain/Auction.js'; import Auction from '../domain/Auction.js';
const auctionsUpdated = (payload) => ({
type: AUCTIONS_UPDATED,
payload,
});
const autionStatusLoadSuccess = (auctions, dispatch) => { const autionStatusLoadSuccess = (auctions) => (dispatch) => {
const payload = List(auctions).map((i) => Auction.fromJS(i)); const payload = List(auctions).map((i) => Auction.fromJS(i));
dispatch({ type: AUCTIONS_UPDATED, payload }); dispatch(auctionsUpdated(payload));
dispatch(unblockUI);
}; };
export const fetchAuctionStatus = () => (dispatch, getState) => { export const fetchAuctionStatus = () => (dispatch, getState) => {
const state = getState(); const authToken = getAuthToken(getState());
const activeEvent = state.get('activeEvent'); const eventId = getActiveEventId(getState());
let apiUrl = getEndpointUrl(API_ENDPOINTS.GET_STATUS); return fetchActionStatusApi(eventId, authToken)
apiUrl = apiUrl.replace(/:event_id$/, ''); .then((payload) => dispatch(autionStatusLoadSuccess(payload)))
if (activeEvent) { .catch((err) => console.error('[actions::getStatus]', err));
apiUrl = `${apiUrl}${activeEvent}`;
}
dispatch(blockUI());
return fetch(apiUrl)
.then(response => response.json())
.then(payload => autionStatusLoadSuccess(payload, dispatch))
.catch(err => console.error('[actions::getStatus]', err));
}; };

View File

@@ -1,36 +1,31 @@
import { List } from 'immutable'; import { List } from 'immutable';
import { blockUI, unblockUI } from './index.js';
import { fetchEvents as fetchEventsApi } from '../api/events.js'; import { fetchEvents as fetchEventsApi } from '../api/events.js';
import { import { EVENTS_LOAD_FAILED, EVENTS_LOADED, GET_EVENTS } from '../constants/actionTypes.js';
EVENTS_LOAD_FAILED,
EVENTS_LOADED,
GET_EVENTS,
} from '../constants/actionTypes.js';
import Event from '../domain/Event.js'; import Event from '../domain/Event.js';
import { getAuthToken } from '../selectors/auth.js'; import { getAuthToken } from '../selectors/auth.js';
const beginFetchEvents = () => ({ type: GET_EVENTS });
const eventsLoaded = (payload) => ({ type: EVENTS_LOADED, payload }); const eventsLoaded = (payload) => ({ type: EVENTS_LOADED, payload });
const eventsLoadError = (payload) => ({ type: EVENTS_LOAD_FAILED, payload }); const eventsLoadError = (payload) => ({ type: EVENTS_LOAD_FAILED, payload });
const eventsFetchSuccess = (items) => (dispatch) => { const eventsFetchSuccess = (items) => (dispatch) => {
const payload = List(items).map((i) => Event.fromJS(i)); const payload = List(items).map((i) => Event.fromJS(i));
dispatch(eventsLoaded(payload));
dispatch(eventsLoaded(payload));
dispatch(unblockUI);
}; };
const eventsFetchFailure = (error) => (dispatch) => { const eventsFetchFailure = (error) => (dispatch) => {
console.error('[actions::events::eventsFetchFailure]', error); console.error('[actions::events::eventsFetchFailure]', error);
dispatch(eventsLoadError(error)); dispatch(eventsLoadError(error));
dispatch(unblockUI);
}; };
export const fetchEvents = () => (dispatch, getState) => { export const fetchEvents = () => (dispatch, getState) => {
const authToken = getAuthToken(getState()); const authToken = getAuthToken(getState());
fetchEventsApi(authToken) dispatch(beginFetchEvents());
.then(payload => dispatch(eventsFetchSuccess(payload))) fetchEventsApi(authToken)
.catch(err => dispatch(eventsFetchFailure(err))); .then((payload) => dispatch(eventsFetchSuccess(payload)))
.catch((err) => dispatch(eventsFetchFailure(err)));
}; };

View File

@@ -1,12 +1,9 @@
import { import { BLOCK_UI, UNBLOCK_UI } from '../constants/actionTypes.js';
BLOCK_UI,
UNBLOCK_UI,
} from '../constants/actionTypes.js';
export const blockUI = () => ({ export const blockUI = () => ({
type: BLOCK_UI, type: BLOCK_UI,
}); });
export const unblockUI = () => ({ export const unblockUI = () => ({
type: UNBLOCK_UI, type: UNBLOCK_UI,
}); });

View File

@@ -1,39 +1,34 @@
import { List } from 'immutable'; import { List } from 'immutable';
import { fetchItems as fetchItemsApi } from '../api/items.js'; import { fetchItems as fetchItemsApi } from '../api/items.js';
import { import { GET_ITEMS, ITEMS_LOAD_FAILED, ITEMS_LOADED } from '../constants/actionTypes.js';
GET_ITEMS,
ITEMS_LOADED,
} from '../constants/actionTypes.js';
import { getActiveEventId } from '../selectors/activeEvent.js'; import { getActiveEventId } from '../selectors/activeEvent.js';
import { getAuthToken } from '../selectors/auth.js'; import { getAuthToken } from '../selectors/auth.js';
import { blockUI, unblockUI } from './index.js';
import Item from '../domain/Item.js'; import Item from '../domain/Item.js';
const beginItemLoad = () => ({ type: GET_ITEMS });
const itemsLoaded = (payload) => ({ type: ITEMS_LOADED, payload }); const itemsLoaded = (payload) => ({ type: ITEMS_LOADED, payload });
const itemsLoadError = (payload) => ({ type: ITEMS_LOAD_FAILED, payload }); const itemsLoadFailure = (payload) => ({ type: ITEMS_LOAD_FAILED, payload });
const itemsFetchSuccess = (items) => (dispatch) => { const itemsFetchSuccess = (items) => (dispatch) => {
const payload = List(items).map((i) => Item.fromJS(i)); const payload = List(items).map((i) => Item.fromJS(i));
dispatch(itemsLoaded(payload));
dispatch(itemsLoaded(payload));
dispatch(unblockUI);
}; };
const itemsFetchFailure = (error) => (dispatch) => { const itemsFetchFailure = (error) => (dispatch) => {
console.error('[actions::items::itemsFetchFailure]', error); console.error('[actions::items::itemsFetchFailure]', error);
dispatch(itemsLoadFailure(error)); dispatch(itemsLoadFailure(error));
dispatch(unblockUI);
}; };
export const fetchItems = () => (dispatch, getState) => { export const fetchItems = () => (dispatch, getState) => {
const eventId = getActiveEventId(getState()); const authToken = getAuthToken(getState());
const authToken = getAuthToken(getState()); const eventId = getActiveEventId(getState());
fetchItemsApi(activeEvent, authToken) dispatch(beginItemLoad());
.then(payload => dispatch(itemsFetchSuccess(payload))) fetchItemsApi(eventId, authToken)
.catch(err => dispatch(itemsFetchFailure(err))); .then((payload) => dispatch(itemsFetchSuccess(payload)))
.catch((err) => dispatch(itemsFetchFailure(err)));
}; };

View File

@@ -1,124 +1,117 @@
import { blockUI, unblockUI } from './index.js'; import { blockUI, unblockUI } from './index.js';
import { import {
getEmailAvailability, getEmailAvailability,
getNomAvailaibility, getNomAvailaibility,
loginUser, loginUser,
registerNewUser, registerNewUser,
} from '../api/profile.js'; } from '../api/profile.js';
import { import {
DO_LOGOUT, DO_LOGOUT,
LOGIN_FAILURE, LOGIN_FAILURE,
LOGIN_SUCCESS, LOGIN_SUCCESS,
PROFILE_EMAIL_AVAILABLE, PROFILE_EMAIL_AVAILABLE,
PROFILE_NOM_AVAILABLE, PROFILE_NOM_AVAILABLE,
UNSET_AUTH, REGISTRATION_FAILURE,
UNSET_PROFILE, REGISTRATION_SUCCESS,
UPDATE_PROFILE, UNSET_AUTH,
UNSET_PROFILE,
UPDATE_PROFILE,
} from '../constants/actionTypes.js'; } from '../constants/actionTypes.js';
const isValidEmail = (payload) => ({ const isValidEmail = (payload) => ({
type: PROFILE_EMAIL_AVAILABLE, type: PROFILE_EMAIL_AVAILABLE,
payload, payload,
}); });
const isValidNom = (payload) => ({ const isValidNom = (payload) => ({
type: PROFILE_NOM_AVAILABLE, type: PROFILE_NOM_AVAILABLE,
payload, payload,
}); });
const loginFailure = (payload) => ({ const loginFailure = (payload) => ({
type: LOGIN_FAILURE, type: LOGIN_FAILURE,
payload, payload,
}); });
const loginSuccess = (payload) => ({ const loginSuccess = (payload) => ({
type: LOGIN_SUCCESS, type: LOGIN_SUCCESS,
payload, payload,
}); });
const logoutUser = () => ({ const logoutUser = () => ({
type: DO_LOGOUT, type: DO_LOGOUT,
}); });
const registrationFailure = (payload) => ({ const registrationFailure = (payload) => ({
type: REGISTRATION_FAILURE, type: REGISTRATION_FAILURE,
payload, payload,
}); });
const registrationSuccess = (payload) => ({ const registrationSuccess = (payload) => ({
type: REGISTRATION_SUCCESS, type: REGISTRATION_SUCCESS,
payload, payload,
}); });
const unsetAuth = () => ({ const unsetAuth = () => ({
type: UNSET_AUTH, type: UNSET_AUTH,
}); });
const unsetProfile = () => ({ const unsetProfile = () => ({
type: UNSET_PROFILE, type: UNSET_PROFILE,
}); });
const updateProfile = (profile) => ({ const updateProfile = (profile) => ({
type: UPDATE_PROFILE, type: UPDATE_PROFILE,
payload: profile, payload: profile,
}); });
export const checkEmailAvailability = (email) => (dispatch) => { export const checkEmailAvailability = (email) => (dispatch) => {};
}; export const checkNomAvailability = (nomDeBid) => (dispatch) => {};
export const checkNomAvailability = (nomDeBid) => (dispatch) => {
};
export const login = (username, password) => (dispatch) => { export const login = (username, password) => (dispatch) => {
dispatch(blockUI()); dispatch(blockUI());
loginUser(username, password) loginUser(username, password)
.then(result => { .then((result) => {
dispatch(loginSuccess(result)) dispatch(loginSuccess(result));
}) })
.catch(err => dispatch(loginFailure(err))); .catch((err) => dispatch(loginFailure(err)));
}; };
export const logout = () => (dispatch) => { export const logout = () => (dispatch) => {
dispatch(unsetProfile()); dispatch(unsetProfile());
dispatch(unsetAuth()); dispatch(unsetAuth());
dispatch(logoutUser()); dispatch(logoutUser());
}; };
// USER REGISTRATION // USER REGISTRATION
const handleRegistrationSuccess = (user) => (dispatch) => { const handleRegistrationSuccess = (user) => (dispatch) => {
dispatch(unblockUI()); dispatch(unblockUI());
dispatch(registrationSuccess()); dispatch(registrationSuccess());
dispatch(loginSuccess(user)); dispatch(loginSuccess(user));
}; };
const handleRegistrationFailure = (error) => (dispatch) => { const handleRegistrationFailure = (error) => (dispatch) => {
dispatch(unblockUI()); dispatch(unblockUI());
dispatch(registrationFailure(error)); dispatch(registrationFailure(error));
}; };
export const registerUser = (user) => (dispatch) => { export const registerUser = (user) => (dispatch) => {
dispatch(blockUI()); dispatch(blockUI());
registerNewUser(user) registerNewUser(user)
.then(user => dispatch(handleRegistrationSuccess(user))) .then((user) => dispatch(handleRegistrationSuccess(user)))
.catch(err => dispatch(handleRegistrationFailure(err))); .catch((err) => dispatch(handleRegistrationFailure(err)));
}; };
// FACEBOOK // FACEBOOK
export const facebookLoginSuccess = (result) => (dispatch) => { export const facebookLoginSuccess = (result) => (dispatch) => {
console.log('facebookLoginSuccess', result); console.log('facebookLoginSuccess', result);
dispatch(facebookLoginSuccess(result)); dispatch(facebookLoginSuccess(result));
}; };
// LOGIN SERVICES COMMON ACTIONS // LOGIN SERVICES COMMON ACTIONS
export const serviceRegistrationError = (error) => (dispatch) => { export const serviceRegistrationError = (error) => (dispatch) => {};
}; export const serviceRegistrationCanceled = () => (dispatch) => {};
export const serviceRegistrationCanceled = () => (dispatch) => {
};

7
app/api/auctionStatus.js Normal file
View File

@@ -0,0 +1,7 @@
import { API_ENDPOINTS, requestGet } from './index.js';
export const fetchAuctionStatus = (eventId, auth) => {
const path = `${API_ENDPOINTS.GET_STATUS})/${eventId}`;
const opts = { Authorization: auth ? `Bearer ${auth}` : null };
return requestGet(path, null, opts);
};

10
app/api/bid.js Normal file
View File

@@ -0,0 +1,10 @@
import { API_ENDPOINTS, requestPost } from './index.js';
export const placeBid = ({ bidAmount, bidderId, eventId, itemId, maxAmount }, auth) => {
const requestOptions = { Authorization: auth ? `Bearer ${auth}` : null };
return requestPost({
path: `${API_ENDPOINTS.PLACE_BID}/${itemId}`,
body: { bidAmount, bidderId, eventId, maxAmount },
requestOptions,
});
};

View File

@@ -1,6 +1,6 @@
import { API_ENDPOINTS, requestGet } from './index.js'; import { API_ENDPOINTS, requestGet } from './index.js';
export const fetchEvents = (auth) => { export const fetchEvents = (auth) => {
const opts = { Authorization: auth ? `Bearer ${auth}` : null }; const opts = { Authorization: auth ? `Bearer ${auth}` : null };
return requestGet(API_ENDPOINTS.GET_EVENTS, null, opts); return requestGet(API_ENDPOINTS.GET_EVENTS, null, opts);
}; };

View File

@@ -1,21 +1,23 @@
import { API_URL } from '../constants/constants.js'; import { API_URL } from '../constants/constants.js';
export const constructUrl = (path, params, host = API_URL) => { export const constructUrl = (path, params, host = API_URL) => {
if (!(/^\//g).test(path)) { if (!/^\//g.test(path)) {
throw new Error(`Invalid path! Expecting a valid relative path (path needs to start with /)(${path})`); throw new Error(
} `Invalid path! Expecting a valid relative path (path needs to start with /)(${path})`,
);
}
let url = path; let url = path;
if (host) { if (host) {
url = `${host}${url}`; url = `${host}${url}`;
} }
if (params) { if (params) {
url = `${url}?${params}`; url = `${url}?${params}`;
} }
return url; return url;
}; };
/** /**
@@ -26,59 +28,57 @@ export const constructUrl = (path, params, host = API_URL) => {
* this serializes data in a way that helps support legacy endpoints * this serializes data in a way that helps support legacy endpoints
*/ */
export const formatPostData = (body) => { export const formatPostData = (body) => {
const postData = new FormData(); const postData = new FormData();
Object.keys(body).forEach(key => postData.append(key, body[key])); Object.keys(body).forEach((key) => postData.append(key, body[key]));
return postData; return postData;
}; };
const parseQueryParamsString = (queryParams) => { const parseQueryParamsString = (queryParams) => {
if (typeof queryParams !== 'string') { if (typeof queryParams !== 'string') {
return null; return null;
} }
return queryParams; return queryParams;
}; };
const parseQueryParamsObject = (queryParams) => { const parseQueryParamsObject = (queryParams) => {
if (queryParams === null || typeof queryParams !== 'object' || Array.isArray(queryParams)) { if (queryParams === null || typeof queryParams !== 'object' || Array.isArray(queryParams)) {
return null; return null;
} }
return Object return Object.keys(queryParams)
.keys(queryParams) .map((key) => {
.map((key) => { const value = queryParams[key];
const value = queryParams[key]; return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; })
}) .join('&');
.join('&');
}; };
const parseQueryParamsArray = (queryParams) => { const parseQueryParamsArray = (queryParams) => {
if (!Array.isArray(queryParams)) { if (!Array.isArray(queryParams)) {
return null; return null;
} }
return queryParams return queryParams
.map((param) => { .map((param) => {
const [key, value] = param.split('='); const [key, value] = param.split('=');
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
}) })
.join('&'); .join('&');
}; };
export const parseQueryParams = queryParams => ( export const parseQueryParams = (queryParams) =>
parseQueryParamsString(queryParams) || parseQueryParamsString(queryParams) ||
parseQueryParamsObject(queryParams) || parseQueryParamsObject(queryParams) ||
parseQueryParamsArray(queryParams) || parseQueryParamsArray(queryParams) ||
'' '';
);
export const request = (url, options) => { export const request = (url, options) => {
try { try {
return fetch(url, options).catch((err) => console.error(err)); return fetch(url, options).catch((err) => console.error(err));
} catch (error) { } catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
}; };
export const unwrapJson = (response) => response.json(); export const unwrapJson = (response) => response.json();

View File

@@ -1,95 +1,95 @@
import { import {
constructUrl, constructUrl,
formatPostData, formatPostData,
parseQueryParams, parseQueryParams,
request, request,
unwrapJson, unwrapJson,
validateResponse, validateResponse,
} from './helpers.js'; } from './helpers.js';
import { API_URL } from '../constants/constants.js'; import { API_URL } from '../constants/constants.js';
const DefaultRequestOptions = {}; const DefaultRequestOptions = {};
const endpoints = { export const API_ENDPOINTS = {
// Events and Items // Events and Items
GET_EVENTS: '/events', GET_EVENTS: '/events',
// GET_ITEMS: '/items?eventId=:event_id', // GET_ITEMS: '/items?eventId=:event_id',
GET_ITEMS: '/items', GET_ITEMS: '/items',
// Auction Interactions // Auction Interactions
// GET_STATUS: '/auction/:event_id', // GET_STATUS: '/auction/:event_id',
GET_STATUS: '/auction', GET_STATUS: '/auction',
PLACE_BID: '/bids/:item_id', PLACE_BID: '/bids',
PURCHASE_ITEM: '/sales', PURCHASE_ITEM: '/sales',
// User/Profile // User/Profile
USER_SIGNUP: '/signup', USER_SIGNUP: '/signup',
USER_PROFILE: '/users/:user_id', USER_PROFILE: '/users/:user_id',
VALIDATE_SIGNUP_EMAIL: '/signup/validate/email', VALIDATE_SIGNUP_EMAIL: '/signup/validate/email',
VALIDATE_SIGNUP_NOM: '/signup/validate/nom', VALIDATE_SIGNUP_NOM: '/signup/validate/nom',
// Services // Services
APPLE_SIGNUP: '/auth/apple/login', APPLE_SIGNUP: '/auth/apple/login',
APPLE_LINK: '/auth/apple/link', APPLE_LINK: '/auth/apple/link',
FACEBOOK_SIGNUP: '/auth/facebook/login', FACEBOOK_SIGNUP: '/auth/facebook/login',
FACEBOOK_LINK: '/auth/facebook/link', FACEBOOK_LINK: '/auth/facebook/link',
GOOGLE_SIGNUP: '/auth/google/login', GOOGLE_SIGNUP: '/auth/google/login',
GOOGLE_LINK: '/auth/google/link', GOOGLE_LINK: '/auth/google/link',
}; };
const cacheBuster = () => { const cacheBuster = () => {
const timestamp = String(Date.now()); const timestamp = String(Date.now());
return `?buster=${timestamp}`; return `?buster=${timestamp}`;
}; };
export const getEndpointUrl = (endpoint) => { export const getEndpointUrl = (endpoint) => {
if (!endpoints[endpoint]) { if (!endpoints[endpoint]) {
throw new Error('Invalid API endpoint specified'); throw new Error('Invalid API endpoint specified');
} }
return `${API_URL}${endpoints[endpoint]}`; //`${cacheBuster()}`; return `${API_URL}${endpoints[endpoint]}`; //`${cacheBuster()}`;
}; };
export const requestGet = (path, queryParams = [], requestOptions = {}) => { export const requestGet = (path, queryParams = [], requestOptions = {}) => {
const params = parseQueryParams(queryParams); const params = parseQueryParams(queryParams);
if (params === null) { if (params === null) {
throw new Error('Invalid queryParams'); throw new Error('Invalid queryParams');
} }
return request(constructUrl(path, params), { return request(constructUrl(path, params), {
...DefaultRequestOptions, ...DefaultRequestOptions,
...requestOptions ...requestOptions,
}) })
.then(validateResponse) .then(validateResponse)
.then(unwrapJson); .then(unwrapJson);
}; };
export const requestPost = (options) => { export const requestPost = (options) => {
const { const {
path, path,
body = {}, body = {},
queryParams = [], queryParams = [],
requestOptions = {}, requestOptions = {},
isFormattedPostData = false isFormattedPostData = false,
} = options; } = options;
const params = parseQueryParams(queryParams || []); const params = parseQueryParams(queryParams || []);
if (params === null) { if (params === null) {
throw new Error('invalid queryParams'); throw new Error('invalid queryParams');
} }
// Additional formatting happens in `formatPostData()` // Additional formatting happens in `formatPostData()`
// like handling what jQuery calls `traditional` form data // like handling what jQuery calls `traditional` form data
return request(constructUrl(path, params), { return request(constructUrl(path, params), {
...DefaultRequestOptions, ...DefaultRequestOptions,
...requestOptions, ...requestOptions,
method: 'POST', method: 'POST',
body: isFormattedPostData ? body : formatPostData(body) body: isFormattedPostData ? body : formatPostData(body),
}) })
.then(validateResponse) .then(validateResponse)
.then(unwrapJson); .then(unwrapJson);
}; };

View File

@@ -1,7 +1,7 @@
import { API_ENDPOINTS, requestGet } from './index.js'; import { API_ENDPOINTS, requestGet } from './index.js';
export const fetchItems = (eventId, auth) => { export const fetchItems = (eventId, auth) => {
const path = String(API_ENDPOINTS.GET_ITEMS).replace(/:event_id$/, eventId); const path = String(API_ENDPOINTS.GET_ITEMS).replace(/:event_id$/, eventId);
const opts = { Authorization: auth ? `Bearer ${auth}` : null }; const opts = { Authorization: auth ? `Bearer ${auth}` : null };
return requestGet(path, null, opts); return requestGet(path, null, opts);
}; };

View File

@@ -1,15 +1,19 @@
import { API_ENDPOINTS, requestGet } from './index.js'; import { API_ENDPOINTS, requestGet } from './index.js';
export const getEmailAvailability = (email) => requestGet(`${API_ENDPOINTS.VALIDATE_SIGNUP_EMAIL}/&{encodeURI(email)}`); export const getEmailAvailability = (email) =>
requestGet(`${API_ENDPOINTS.VALIDATE_SIGNUP_EMAIL}/&{encodeURI(email)}`);
export const getNomAvailaibility = (nomDeBid) => requestGet(`${API_ENDPOINTS.VALIDATE_SIGNUP_NOM}/${encodeURI(nomDeBid)}`); export const getNomAvailaibility = (nomDeBid) =>
requestGet(`${API_ENDPOINTS.VALIDATE_SIGNUP_NOM}/${encodeURI(nomDeBid)}`);
export const loginUser = (username, password) => requestPost({ export const loginUser = (username, password) =>
path: API_ENDPOINTS.LOGIN, requestPost({
body: { username, password }, path: API_ENDPOINTS.LOGIN,
}); body: { username, password },
});
export const registerNewUser = (user) => requestPost({ export const registerNewUser = (user) =>
path: API_ENDPOINTS.USER_SIGNUP, requestPost({
body: { user }, path: API_ENDPOINTS.USER_SIGNUP,
}); body: { user },
});

View File

@@ -5,7 +5,10 @@ import { fetchEvents } from '../../actions/events.js';
import AppHeader from './AppHeader.js'; import AppHeader from './AppHeader.js';
const matchDispatchToProps = (dispatch) => ({ const matchDispatchToProps = (dispatch) => ({
fetchEvents: () => dispatch(fetchEvents()), fetchEvents: () => dispatch(fetchEvents()),
}); });
export default connect(null, matchDispatchToProps)(AppHeader); export default connect(
null,
matchDispatchToProps,
)(AppHeader);

View File

@@ -10,27 +10,26 @@ import HeaderContentRight from './HeaderContentRight.container.js';
import styles from './AppHeader.styles.js'; import styles from './AppHeader.styles.js';
export default class AppHeader extends Component { export default class AppHeader extends Component {
static get propTypes() {
return {
fetchEvents: PropTypes.func.isRequired,
};
}
static get propTypes() { componentDidMount() {
return { this.props.fetchEvents();
fetchEvents: PropTypes.func.isRequired, }
};
}
componentDidMount() { render() {
this.props.fetchEvents(); const { navigation } = this.props;
}
render () { return (
const { navigation } = this.props; <Header
placement="left"
return ( leftComponent={<HeaderContentRight navigation={navigation} />}
<Header centerComponent={<HeaderTitle navigation={navigation} />}
placement="left" rightComponent={<HeaderContentLeft navigation={navigation} />}
leftComponent={<HeaderContentRight navigation={navigation} />} />
centerComponent={<HeaderTitle navigation={navigation} />} );
rightComponent={<HeaderContentLeft navigation={navigation} />} }
/>
);
}
} }

View File

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

View File

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

View File

@@ -5,8 +5,11 @@ import { getProfileAvatarUrl } from '../../selectors/profile.js';
import HeaderContentRight from './HeaderContentRight.js'; 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: ownProps.navigation.state.routeName === 'Profile',
}); });
export default connect(matchStateToProps, null)(HeaderContentRight); export default connect(
matchStateToProps,
null,
)(HeaderContentRight);

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,13 +6,16 @@ import { hasMultipleEvents } from '../../../selectors/events.js';
import HeaderTitle from './HeaderTitle.js'; import HeaderTitle from './HeaderTitle.js';
const matchStateToProps = (state, ownProps) => { const matchStateToProps = (state, ownProps) => {
const { routeName } = ownProps.navigation.state; const { routeName } = ownProps.navigation.state;
return { return {
activeRoute: routeName, activeRoute: routeName,
hasActiveEvent: hasActiveEvent(state), hasActiveEvent: hasActiveEvent(state),
hasMultipleEvents: hasMultipleEvents(state), hasMultipleEvents: hasMultipleEvents(state),
}; };
}; };
export default connect(matchStateToProps, null)(HeaderTitle); export default connect(
matchStateToProps,
null,
)(HeaderTitle);

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,10 @@ import { getProfileAvatarUrl } 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),
}); });
export default connect(matchStateToProps, null)(UserProfileButton); export default connect(
matchStateToProps,
null,
)(UserProfileButton);

View File

@@ -7,29 +7,28 @@ import { 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, navigation }) {
const _goToProfile = () => {
navigation.navigate('Profile');
return false;
};
const _goToProfile = () => { return (
navigation.navigate('Profile'); <TouchableOpacity onPress={_goToProfile}>
return false; {avatarUrl !== null ? (
}; <View style={styles.avatarWrap}>
<Image source={{ uri: avatarUrl }} />
return ( </View>
<TouchableOpacity onPress={_goToProfile}> ) : (
{avatarUrl !== null ? ( <Icon name="ei-user" type="evilicons" size={28} />
<View style={styles.avatarWrap}> )}
<Image source={{ uri: avatarUrl }} /> </TouchableOpacity>
</View> );
) : (
<Icon name="ei-user" type="evilicons" size={28} />
)}
</TouchableOpacity>
);
} }
UserProfileButton.propTypes = { UserProfileButton.propTypes = {
avatarUrl: PropTypes.string, avatarUrl: PropTypes.string,
}; };
UserProfileButton.propTypes = { UserProfileButton.propTypes = {
avatarUrl: null, avatarUrl: null,
}; };

View File

@@ -1,13 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { StyleSheet, TouchableOpacity, Text, Image, View } from 'react-native';
StyleSheet,
TouchableOpacity,
Text,
Image,
View
} from 'react-native';
import GallerySwiper from 'react-native-gallery-swiper'; import GallerySwiper from 'react-native-gallery-swiper';
@@ -18,173 +12,173 @@ import { ITEM_TYPES } from '../../constants/constants.js';
import { formatPrice, getAuctionTime } from '../../library/helpers.js'; import { formatPrice, getAuctionTime } from '../../library/helpers.js';
export default class AuctionListItem extends Component { export default class AuctionListItem extends Component {
static get propTypes() { static get propTypes() {
return { return {
description: PropTypes.string, description: PropTypes.string,
donor: PropTypes.string, donor: PropTypes.string,
end: PropTypes.string.isRequired, end: PropTypes.string.isRequired,
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
images: PropTypes.arrayOf( images: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
url: PropTypes.string, url: PropTypes.string,
}), }),
), ),
start: PropTypes.string.isRequired, start: PropTypes.string.isRequired,
startingPrice: PropTypes.number.isRequired, startingPrice: PropTypes.number.isRequired,
subtitle: PropTypes.string, subtitle: PropTypes.string,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
type: 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 });
}; };
}
static get defaultProps() { _viewItemDetail = () => {
return { const { _id: id } = this.props.details;
description: null, this.props.navigation.navigate('Item', { id });
donor: null,
images: null,
subtitle: null,
}; };
}
constructor(props) { render() {
super(props); const {
} description,
donor,
end,
id,
images,
start,
startingPrice,
subtitle,
title,
type,
} = this.props;
_getBidTime = () => { return (
const { end, start } = this.props; <TouchableOpacity onPress={this._viewItemDetail}>
return getAuctionTime({ end, start }); <View style={styles.rowContainer}>
} {images !== null && images.length > 0 && (
<GallerySwiper
_viewItemDetail = () => { enableScale={false}
const { _id: id } = this.props.details; images={images}
this.props.navigation.navigate('Item', { id }); initialNumToRender={2}
} resizeMode="cover"
sensitiveScroll={false}
render() { style={{ height: 280 }}
const { />
description, )}
donor, <View style={styles.rowText}>
end, {type === ITEM_TYPES.AUCTION && <BidStatus itemId={id} />}
id, <Text style={styles.title} numberOfLines={2} ellipsizeMode={'tail'}>
images, {title}
start, </Text>
startingPrice, <Text style={styles.subtitle} numberOfLines={1} ellipsizeMode={'tail'}>
subtitle, {subtitle}
title, </Text>
type, {donor && (
} = this.props; <Text style={styles.donor} numberOfLines={1} ellipsizeMode={'tail'}>
{donor}
return( </Text>
<TouchableOpacity onPress={this._viewItemDetail}> )}
<View style={styles.rowContainer}> {type === ITEM_TYPES.AUCTION ? (
{images !== null && images.length > 0 && ( <AuctionPriceAndBidCount itemId={id} />
<GallerySwiper ) : (
enableScale={false} <Text style={styles.price} numberOfLines={1} ellipsizeMode={'tail'}>
images={images} {formatPrice(startingPrice)}
initialNumToRender={2} </Text>
resizeMode="cover" )}
sensitiveScroll={false} <Text style={styles.timeline} numberOfLines={1}>
style={{height: 280}} {this._getBidTime()}
/> </Text>
)} <Text style={styles.description} numberOfLines={3} ellipsizeMode={'tail'}>
<View style={styles.rowText}> {description}
{type === ITEM_TYPES.AUCTION && <BidStatus itemId={id} />} </Text>
<Text style={styles.title} numberOfLines={2} ellipsizeMode={'tail'}> </View>
{title} </View>
</Text> </TouchableOpacity>
<Text style={styles.subtitle} numberOfLines={1} ellipsizeMode={'tail'}> );
{subtitle} }
</Text>
{donor && (
<Text style={styles.donor} numberOfLines={1} ellipsizeMode={'tail'}>
{donor}
</Text>
)}
{type === ITEM_TYPES.AUCTION ? (
<AuctionPriceAndBidCount itemId={id} />
) : (
<Text style={styles.price} numberOfLines={1} ellipsizeMode={'tail'}>
{formatPrice(startingPrice)}
</Text>
)}
<Text style={styles.timeline} numberOfLines={1}>
{this._getBidTime()}
</Text>
<Text style={styles.description} numberOfLines={3} ellipsizeMode={'tail'}>
{description}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
description: { description: {
color: '#777', color: '#777',
fontSize: 14, fontSize: 14,
marginTop: 5, marginTop: 5,
paddingLeft: 10, paddingLeft: 10,
paddingRight: 10, paddingRight: 10,
}, },
donor: { donor: {
color: '#777', color: '#777',
fontSize: 14, fontSize: 14,
marginTop: 5, marginTop: 5,
paddingLeft: 10, paddingLeft: 10,
}, },
image: { image: {
flex: 1, flex: 1,
height: undefined, height: undefined,
width: undefined, width: undefined,
}, },
price: { price: {
color: '#777', color: '#777',
fontSize: 16, fontSize: 16,
fontWeight: 'bold', fontWeight: 'bold',
paddingLeft: 10, paddingLeft: 10,
paddingTop: 5, paddingTop: 5,
}, },
rowContainer: { rowContainer: {
backgroundColor: '#FFF', backgroundColor: '#FFF',
borderRadius: 4, borderRadius: 4,
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
marginRight: 10, marginRight: 10,
marginLeft: 10, marginLeft: 10,
marginTop: 10, marginTop: 10,
padding: 10, padding: 10,
shadowColor: '#CCC', shadowColor: '#CCC',
shadowOffset: { shadowOffset: {
width: 1, width: 1,
height: 1 height: 1,
},
shadowOpacity: 1.0,
shadowRadius: 1,
},
rowText: {
flex: 4,
flexDirection: 'column',
},
subtitle: {
color: '#777',
fontSize: 14,
marginTop: 5,
paddingLeft: 10,
},
timeline: {
color: '#777',
fontSize: 14,
marginTop: 5,
paddingLeft: 10,
paddingRight: 10,
},
title: {
color: '#777',
fontSize: 16,
fontWeight: 'bold',
paddingLeft: 10,
paddingTop: 5,
}, },
shadowOpacity: 1.0,
shadowRadius: 1,
},
rowText: {
flex: 4,
flexDirection: 'column',
},
subtitle: {
color: '#777',
fontSize: 14,
marginTop: 5,
paddingLeft: 10,
},
timeline: {
color: '#777',
fontSize: 14,
marginTop: 5,
paddingLeft: 10,
paddingRight: 10,
},
title: {
color: '#777',
fontSize: 16,
fontWeight: 'bold',
paddingLeft: 10,
paddingTop: 5,
},
}); });

View File

@@ -3,29 +3,26 @@ import PropTypes from 'prop-types';
import { formatPrice } from '../../library/helpers.js'; import { formatPrice } from '../../library/helpers.js';
import { import { StyleSheet, Text } from 'react-native';
StyleSheet,
Text,
} from 'react-native';
const AuctionPriceAndBidCount = ({ bidCount, currentPrice }) => { const AuctionPriceAndBidCount = ({ bidCount, currentPrice }) => {
return ( return (
<Text style={styles.currentPriceAndBidCount} numberOfLines={1}> <Text style={styles.currentPriceAndBidCount} numberOfLines={1}>
{`${formatPrice(currentPrice)} (${bidCount} bids)`} {`${formatPrice(currentPrice)} (${bidCount} bids)`}
</Text> </Text>
); );
}; };
AuctionPriceAndBidCount.propTypes = { AuctionPriceAndBidCount.propTypes = {
itemId: PropTypes.string.isRequired, itemId: PropTypes.string.isRequired,
bidCount: PropTypes.number.isRequired, bidCount: PropTypes.number.isRequired,
currentPrice: PropTypes.number.isRequired, currentPrice: PropTypes.number.isRequired,
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
currentPriceAndBidCount: { currentPriceAndBidCount: {
color: '#000', color: '#000',
}, },
}); });
export default AuctionPriceAndBidCount; export default AuctionPriceAndBidCount;

View File

@@ -1,44 +1,41 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { StyleSheet, Text } from 'react-native';
StyleSheet,
Text,
} from 'react-native';
const BidStatus = ({ isBidding, isWinning }) => { const BidStatus = ({ isBidding, isWinning }) => {
if (!isBidding) { if (!isBidding) {
return null; return null;
} }
const statusBarStyle = isWinning const statusBarStyle = isWinning
? [ styles.bidStatus, styes.isWinning ] ? [styles.bidStatus, styes.isWinning]
: [ styles.bidStatus, styles.isOutbid ]; : [styles.bidStatus, styles.isOutbid];
return ( return (
<Text style={statusBarStyle} numberOfLines={1}> <Text style={statusBarStyle} numberOfLines={1}>
{isWinning && `Oh no! You have been outbid!`} {isWinning && 'Oh no! You have been outbid!'}
{!isWinning && isBidding && `You have the winning bid! (for now...)`} {!isWinning && isBidding && 'You have the winning bid! (for now...)'}
</Text> </Text>
); );
}; };
BidStatus.propTypes = { BidStatus.propTypes = {
isBidding: PropTypes.bool.isRequired, isBidding: PropTypes.bool.isRequired,
isWinning: PropTypes.bool.isRequired, isWinning: PropTypes.bool.isRequired,
itemId: PropTypes.string.isRequired, itemId: PropTypes.string.isRequired,
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
bidStatus: { bidStatus: {
color: '#fff', color: '#fff',
}, },
isWinning: { isWinning: {
backgroundColor: '#F00', backgroundColor: '#F00',
}, },
isOutbid: { isOutbid: {
backgroundColor: '#0F0', backgroundColor: '#0F0',
}, },
}); });
export default BidStatus; export default BidStatus;

View File

@@ -1,42 +1,38 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { StyleSheet, Text, View } from 'react-native';
StyleSheet,
Text,
View,
} from 'react-native';
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}>Filter</Text>
<Text style={styles.view}>View</Text> <Text style={styles.view}>View</Text>
</View> </View>
); );
FilterBar.propTypes = { FilterBar.propTypes = {
changeFilterer: PropTypes.func.isRequired, changeFilterer: PropTypes.func.isRequired,
changeViewMode: PropTypes.func.isRequired, changeViewMode: PropTypes.func.isRequired,
filterMode: PropTypes.string, filterMode: PropTypes.string,
viewMode: PropTypes.string, viewMode: PropTypes.string,
}; };
FilterBar.defaultProps = { FilterBar.defaultProps = {
filterMode: null, filterMode: null,
viewMode: null, viewMode: null,
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
filterBar: { filterBar: {
backgroundColor: '#0F0', backgroundColor: '#0F0',
flexDirection: 'row', flexDirection: 'row',
}, },
filter: { filter: {
flex: 2, flex: 2,
}, },
view: { view: {
flex: 2, flex: 2,
}, },
}); });
export default FilterBar; export default FilterBar;

View File

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

View File

@@ -1,19 +1,22 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
facebookLoginSuccess, facebookLoginSuccess,
logout, logout,
registrationServiceError, registrationServiceError,
userCanceledRegistration, userCanceledRegistration,
} from '../../actions/profile.js'; } from '../../actions/profile.js';
import FacebookLogin from './FacebookLogin.js'; import FacebookLogin from './FacebookLogin.js';
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
doCancelAction: () => dispatch(userCanceledRegistration()), doCancelAction: () => dispatch(userCanceledRegistration()),
doErrorAction: (error) => dispatch(registrationServiceError(error)), doErrorAction: (error) => dispatch(registrationServiceError(error)),
doLogoutAction: () => dispatch(logout()), doLogoutAction: () => dispatch(logout()),
doSuccessAction: (result) => dispatch(facebookLoginSuccess(result)), doSuccessAction: (result) => dispatch(facebookLoginSuccess(result)),
}); });
export default connect(null, mapDispatchToProps)(FacebookLogin); export default connect(
null,
mapDispatchToProps,
)(FacebookLogin);

View File

@@ -6,36 +6,33 @@ import { LoginButton } from 'react-native-fbsdk';
import { PERMISSIONS } from '../../constants/constants.js'; import { PERMISSIONS } from '../../constants/constants.js';
export default function FacebookLogin({ export default function FacebookLogin({
doCancelAction, doCancelAction,
doErrorAction, doErrorAction,
doLogoutAction, doLogoutAction,
doSuccessAction, doSuccessAction,
}) { }) {
return (
return ( <View>
<View> <LoginButton
<LoginButton publishPermissions={PERMISSIONS.FACEBOOK}
publishPermissions={PERMISSIONS.FACEBOOK} onLoginFinished={(error, result) => {
onLoginFinished={ if (error) {
(error, result) => { doErrorAction(error);
if (error) { } else if (result.isCancelled) {
doErrorAction(error); doCancelAction();
} else if (result.isCancelled) { } else {
doCancelAction(); doSuccessAction(result);
} else { }
doSuccessAction(result); }}
} onLogoutFinished={doLogoutAction}
} />
} </View>
onLogoutFinished={doLogoutAction} );
/>
</View>
);
} }
FacebookLogin.propTypes = { FacebookLogin.propTypes = {
doCancelAction: PropTypes.func.isRequired, doCancelAction: PropTypes.func.isRequired,
doErrorAction: PropTypes.func.isRequired, doErrorAction: PropTypes.func.isRequired,
doLogoutAction: PropTypes.func.isRequired, doLogoutAction: PropTypes.func.isRequired,
doSuccessAction: PropTypes.func.isRequired, doSuccessAction: PropTypes.func.isRequired,
}; };

View File

@@ -5,7 +5,10 @@ import { login } from '../../actions/profile.js';
import LocalLogin from './LocalLogin.js'; import LocalLogin from './LocalLogin.js';
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
doLoginAction: (username, password) => dispatch(login(username, password)), doLoginAction: (username, password) => dispatch(login(username, password)),
}); });
export default connect(null, mapDispatchToProps)(LocalLogin); export default connect(
null,
mapDispatchToProps,
)(LocalLogin);

View File

@@ -54,5 +54,5 @@ export default function LocalLogin({ doLoginAction }) {
} }
LocalLogin.propTypes = { LocalLogin.propTypes = {
doLoginAction: PropTypes.func.isRequired, doLoginAction: PropTypes.func.isRequired,
}; };

View File

@@ -1,53 +1,53 @@
export const ITEM_FILTERS = { export const ITEM_FILTERS = {
ALL: 'ALL', ALL: 'ALL',
}; };
export const ITEM_TYPES = { export const ITEM_TYPES = {
AUCTION: 'auction', AUCTION: 'auction',
DONATION: 'donation', DONATION: 'donation',
EVENT: 'event', EVENT: 'event',
MEMBERSHIP: 'membership', MEMBERSHIP: 'membership',
POPUP: 'popup', POPUP: 'popup',
RAFFLE: 'raffle', RAFFLE: 'raffle',
TICKET: 'ticket', TICKET: 'ticket',
}; };
export const SORT_MODES = { export const SORT_MODES = {
BIDDERS_ASC: 'BIDDERS_ASC', BIDDERS_ASC: 'BIDDERS_ASC',
BIDDERS_DSC: 'BIDDERS_DSC', BIDDERS_DSC: 'BIDDERS_DSC',
ENDING_ASC: 'ENDING_ASC', ENDING_ASC: 'ENDING_ASC',
ENDING_DSC: 'ENDING_DSC', ENDING_DSC: 'ENDING_DSC',
PRICE_ASC: 'PRICE_ASC', PRICE_ASC: 'PRICE_ASC',
PRICE_DSC: 'PRICE_DSC', PRICE_DSC: 'PRICE_DSC',
TITLE_ASC: 'TITLE_ASC', TITLE_ASC: 'TITLE_ASC',
TITLE_DSC: 'TITLE_DSC', TITLE_DSC: 'TITLE_DSC',
}; };
export const AUCTION_VIEW_MODES = { export const AUCTION_VIEW_MODES = {
ALL: 'ALL', ALL: 'ALL',
BIDDING: 'BIDDING', BIDDING: 'BIDDING',
NO_BIDS: 'NO_BIDS', NO_BIDS: 'NO_BIDS',
WINNING: 'WINNING', WINNING: 'WINNING',
}; };
export const API_URL = 'http://localhost:3001'; export const API_URL = 'http://localhost:3001';
export const API_ENDPOINTS = { export const API_ENDPOINTS = {
GET_EVENTS: 'GET_EVENTS', GET_EVENTS: 'GET_EVENTS',
GET_ITEMS: 'GET_ITEMS', GET_ITEMS: 'GET_ITEMS',
GET_STATUS: 'GET_STATUS', GET_STATUS: 'GET_STATUS',
PLACE_BID: 'PLACE_BID', PLACE_BID: 'PLACE_BID',
PURCHASE_ITEM: 'PURCHASE_ITEM', PURCHASE_ITEM: 'PURCHASE_ITEM',
USER_SIGNUP: 'USER_SIGNUP', USER_SIGNUP: 'USER_SIGNUP',
USER_PROFILE: 'USER_PROFILE', USER_PROFILE: 'USER_PROFILE',
APPLE_SIGNUP: 'APPLE_SIGNUP', APPLE_SIGNUP: 'APPLE_SIGNUP',
APPLE_LINK: 'APPLE_LINK', APPLE_LINK: 'APPLE_LINK',
FACEBOOK_SIGNUP: 'FACEBOOK_SIGNUP', FACEBOOK_SIGNUP: 'FACEBOOK_SIGNUP',
FACEBOOK_LINK: 'FACEBOOK_LINK', FACEBOOK_LINK: 'FACEBOOK_LINK',
GOOGLE_SIGNUP: 'GOOGLE_SIGNUP', GOOGLE_SIGNUP: 'GOOGLE_SIGNUP',
GOOGLE_LINK: 'GOOGLE_LINK', GOOGLE_LINK: 'GOOGLE_LINK',
}; };
export const PERMISSIONS = { export const PERMISSIONS = {
FACEBOOK: [ 'email', 'public_profile' ], FACEBOOK: ['email', 'public_profile'],
}; };

View File

@@ -5,25 +5,27 @@ import { placeBid } from '../../actions/auction.js';
import AuctionListItem from '../../components/Auction/AuctionListItem.js'; import AuctionListItem from '../../components/Auction/AuctionListItem.js';
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { item } = ownProps; const { item } = ownProps;
return { return {
description: item.get('description'), description: item.get('description'),
donor: item.get('donor'), donor: item.get('donor'),
end: item.get('end'), end: item.get('end'),
id: item.get('id'), id: item.get('id'),
images: item.get('images').toArray(), images: item.get('images').toArray(),
start: item.get('start'), start: item.get('start'),
startingPrice: item.get('startingPrice'), startingPrice: item.get('startingPrice'),
subtitle: item.get('subtitle'), subtitle: item.get('subtitle'),
title: item.get('title'), title: item.get('title'),
type: item.get('type'), type: item.get('type'),
}; };
}; };
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
placeBid: (data) => dispatch(placeBid(data)), placeBid: (data) => dispatch(placeBid(data)),
}); });
export default connect(mapStateToProps, null)(AuctionListItem); export default connect(
mapStateToProps,
null,
)(AuctionListItem);

View File

@@ -5,12 +5,15 @@ import { getItemBidCount, getItemPrice } from '../../selectors/auctions.js';
import AuctionPriceAndBidCount from '../../components/Auction/AuctionPriceAndBidCount.js'; import AuctionPriceAndBidCount from '../../components/Auction/AuctionPriceAndBidCount.js';
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const { itemId } = ownProps; const { itemId } = ownProps;
return { return {
bidCount: getItemBidCount(state, itemId), bidCount: getItemBidCount(state, itemId),
currentPrice: getItemPrice(state, itemId), currentPrice: getItemPrice(state, itemId),
}; };
} }
export default connect(mapStateToProps, null)(AuctionPriceAndBidCount); export default connect(
mapStateToProps,
null,
)(AuctionPriceAndBidCount);

View File

@@ -5,12 +5,15 @@ import { isBiddingItem, isWinningItem } from '../../selectors/auctions.js';
import AuctionPriceAndBidCount from '../../components/Auction/BidStatus.js'; import AuctionPriceAndBidCount from '../../components/Auction/BidStatus.js';
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const { itemId } = ownProps; const { itemId } = ownProps;
return { return {
isBidding: isBiddingItem(state, itemId), isBidding: isBiddingItem(state, itemId),
isWinning: isWinningItem(state, itemId), isWinning: isWinningItem(state, itemId),
}; };
} }
export default connect(mapStateToProps, null)(AuctionPriceAndBidCount); export default connect(
mapStateToProps,
null,
)(AuctionPriceAndBidCount);

View File

@@ -6,14 +6,17 @@ import { getEventsAsList } from '../selectors/events.js';
import Events from '../screens/Events.js'; import Events from '../screens/Events.js';
const matchStateToProps = (state) => { const matchStateToProps = (state) => {
const events = getEventsAsList(state); const events = getEventsAsList(state);
console.log('events:', events); console.log('events:', events);
return { events }; return { events };
}; };
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
fetchEvents: () => dispatch(fetchEvents(dispatch)), fetchEvents: () => dispatch(fetchEvents(dispatch)),
}); });
export default connect(matchStateToProps, mapDispatchToProps)(Events); export default connect(
matchStateToProps,
mapDispatchToProps,
)(Events);

View File

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

View File

@@ -1,16 +1,16 @@
import { Record } from 'immutable'; import { Record } from 'immutable';
export default class Auction extends Record({ export default class Auction extends Record({
id: null, id: null,
bidCount: 0, bidCount: 0,
isBidding: false, isBidding: false,
isWinning: false, isWinning: false,
itemPrice: 0, itemPrice: 0,
}) {} }) {}
Auction.fromJS = (data = {}) => { Auction.fromJS = (data = {}) => {
return new Auction({ return new Auction({
id: data._id, id: data._id,
...data, ...data,
}); });
}; };

View File

@@ -4,36 +4,36 @@ import Post from './Post.js';
import TicketClass from './TicketClass.js'; import TicketClass from './TicketClass.js';
export default class Event extends Record({ export default class Event extends Record({
id: null, id: null,
isTicketed: false, isTicketed: false,
requireLoginToSeeAuction: false, requireLoginToSeeAuction: false,
description: null, description: null,
endTime: null, endTime: null,
images: new List(), images: new List(),
posts: new List(), posts: new List(),
showFrom: null, showFrom: null,
showUntil: null, showUntil: null,
startTime: null, startTime: null,
tagline: null, tagline: null,
title: null, title: null,
url: null, url: null,
ticketClasses: new List(), ticketClasses: new List(),
}) { }) {
get isSoldOut() { get isSoldOut() {
if (this.isTicketed) { if (this.isTicketed) {
return false; return false;
} }
return this.ticketClasses.find(t => t.available > 0) || false; return this.ticketClasses.find((t) => t.available > 0) || false;
} }
} }
Event.fromJS = (data = {}) => { Event.fromJS = (data = {}) => {
return new Event({ return new Event({
id: data._id, id: data._id,
...data, ...data,
images: new List(data.images), images: new List(data.images),
posts: new List(data.posts.map(p => Post.fromJS(p))), posts: new List(data.posts.map((p) => Post.fromJS(p))),
ticketClasses: new List(data.ticketClasses.map(t => TicketClass.fromJS(t))), ticketClasses: new List(data.ticketClasses.map((t) => TicketClass.fromJS(t))),
}); });
}; };

View File

@@ -1,43 +1,43 @@
import { List, Record } from 'immutable'; import { List, Record } from 'immutable';
export default class Item extends Record({ export default class Item extends Record({
bidCount: 0, bidCount: 0,
bidIncrement: 10, bidIncrement: 10,
catalogNumber: null, catalogNumber: null,
currentPrice: 0, currentPrice: 0,
description: null, description: null,
donor: null, donor: null,
end: null, end: null,
estimatedValue: null, estimatedValue: null,
eventId: null, eventId: null,
hideAfterEnd: false, hideAfterEnd: false,
hideBeforeStart: false, hideBeforeStart: false,
id: null, id: null,
images: new List(), images: new List(),
isShippable: false, isShippable: false,
notifyOnAvailable: false, notifyOnAvailable: false,
quantityAvailable: 1, quantityAvailable: 1,
soldCount: 0, soldCount: 0,
start: null, start: null,
startingPrice: null, startingPrice: null,
subtitle: null, subtitle: null,
title: null, title: null,
type: null, type: null,
shippingCost: 0, shippingCost: 0,
}) { }) {
get isSoldOut() { get isSoldOut() {
return this.quantityAvailable > this.soldCount; return this.quantityAvailable > this.soldCount;
} }
get totalWithShipping() { get totalWithShipping() {
return this.currentPrice + this.shippingCost; return this.currentPrice + this.shippingCost;
} }
} }
Item.fromJS = (data = {}) => { Item.fromJS = (data = {}) => {
return new Item({ return new Item({
id: data._id, id: data._id,
...data, ...data,
images: List(data.images), images: List(data.images),
}); });
}; };

View File

@@ -1,20 +1,19 @@
import { Record } from 'immutable'; import { Record } from 'immutable';
export default class Post extends Record({ export default class Post extends Record({
author: null, author: null,
content: null, content: null,
id: null, id: null,
isPublic: false, isPublic: false,
scheduledPost: false, scheduledPost: false,
sendNotification: false, sendNotification: false,
timestamp: null, timestamp: null,
title: null, title: null,
}) {}; }) {}
Post.fromJS = (data = {}) => { Post.fromJS = (data = {}) => {
return new Post({ return new Post({
id: data._id, id: data._id,
...data, ...data,
}); });
}; };

View File

@@ -1,44 +1,48 @@
import { List, Record } from 'immutable'; import { List, Record } from 'immutable';
export default class Profile extends Record({ export default class Profile extends Record({
addresses: new List(), addresses: new List(),
avatar: null, avatar: null,
email: null, email: null,
firstName: null, firstName: null,
generatedNomDeBid: false, generatedNomDeBid: false,
hasLinkedApple: false, hasLinkedApple: false,
hasLinkedFacebook: false, hasLinkedFacebook: false,
hasLinkedGoogle: false, hasLinkedGoogle: false,
hasLocalAccount: false, hasLocalAccount: false,
id: null, id: null,
isAllowedToBid: false, isAllowedToBid: false,
isOrganizationEmployee: false, isOrganizationEmployee: false,
isVerified: false, isVerified: false,
lastName: null, lastName: null,
nomDeBid: null, nomDeBid: null,
organizationIdentifier: null, organizationIdentifier: null,
paymentToken: null, paymentToken: null,
phones: new List(), phones: new List(),
}) { }) {
get canBid() { get canBid() {
return this.isAllowedToBid && this.paymentToken !== null; return this.isAllowedToBid && this.paymentToken !== null;
} }
get fullName() { get fullName() {
return `${this.firstName} ${this.lastName}`; return `${this.firstName} ${this.lastName}`;
} }
get isRegisteredAccount() { get isRegisteredAccount() {
return this.hasLinkedApple || return (
this.hasLinkedFacebook || this.hasLinkedGoogle || this.hasLocalAccount; this.hasLinkedApple ||
} this.hasLinkedFacebook ||
this.hasLinkedGoogle ||
this.hasLocalAccount
);
}
} }
Profile.fromJS = (data = {}) => { Profile.fromJS = (data = {}) => {
return new Profile({ return new Profile({
id: data._id, id: data._id,
...data, ...data,
addresses: new List(data.addresses), addresses: new List(data.addresses),
phones: new List(data.phones), phones: new List(data.phones),
}); });
}; };

View File

@@ -1,27 +1,27 @@
import { List, Record } from 'immutable'; import { List, Record } from 'immutable';
export default class TicketClass extends Record({ export default class TicketClass extends Record({
available: 0, available: 0,
capacity: 0, capacity: 0,
endSale: null, endSale: null,
id: null, id: null,
itemId: null, itemId: null,
name: null, name: null,
price: 0, price: 0,
startSale: null, startSale: null,
}) { }) {
get isAlmostGone() { get isAlmostGone() {
return this.available < (this.capacity * 0.20); return this.available < this.capacity * 0.2;
} }
get isSoldOut() { get isSoldOut() {
return this.available === 0; return this.available === 0;
} }
} }
TicketClass.fromJS = (data = {}) => { TicketClass.fromJS = (data = {}) => {
return new TicketClass({ return new TicketClass({
id: data._id, id: data._id,
...data, ...data,
}); });
}; };

View File

@@ -5,60 +5,60 @@ export const formatPrice = (price, format = '$ 0,0[.]00') => {
}; };
export const getAuctionTime = ({ end, start }) => { export const getAuctionTime = ({ end, start }) => {
const now = new Date(); const now = new Date();
const compareToEnd = new Date(end); const compareToEnd = new Date(end);
const compareToStart = new Date(start); const compareToStart = new Date(start);
let delta; let delta;
if (now < start) { if (now < start) {
delta = start - now; delta = start - now;
return 'Bidding starts in ${parseTimeDifferential(delta)}'; return 'Bidding starts in ${parseTimeDifferential(delta)}';
} }
if (now > start && now < end) { if (now > start && now < end) {
delta = end - now; delta = end - now;
return 'Bidding ends in ${parseTimeDifferential(delta)}'; return 'Bidding ends in ${parseTimeDifferential(delta)}';
} }
return 'Bidding has ended'; return 'Bidding has ended';
}; };
export const parseHumanReadableTimeDifferential = (delta) => { export const parseHumanReadableTimeDifferential = (delta) => {
const oneMinute = 60 * 1000; const oneMinute = 60 * 1000;
const oneHour = oneMinute * 60; const oneHour = oneMinute * 60;
const oneDay = oneHour * 24; const oneDay = oneHour * 24;
const oneWeek = oneDay * 7; const oneWeek = oneDay * 7;
const oneMonth = oneWeek * 4; const oneMonth = oneWeek * 4;
let compare = delta / oneMonth; let compare = delta / oneMonth;
if (compare >= 1) { if (compare >= 1) {
compare = Math.floor(compare); compare = Math.floor(compare);
return `${compare} month${compare > 1 ? 's' : ''}`; return `${compare} month${compare > 1 ? 's' : ''}`;
} }
compare = delta / oneWeek; compare = delta / oneWeek;
if (compare >= 1) { if (compare >= 1) {
compare = Math.floor(compare); compare = Math.floor(compare);
return `${compare} week${compare > 1 ? 's' : ''}`; return `${compare} week${compare > 1 ? 's' : ''}`;
} }
compare = delta / oneDay; compare = delta / oneDay;
if (compare > 1) { if (compare > 1) {
compare = Math.floor(compare); compare = Math.floor(compare);
return `${compare} day${compare > 1 ? 's' : ''}`; return `${compare} day${compare > 1 ? 's' : ''}`;
} }
compare = delta / oneHour; compare = delta / oneHour;
if (compare > 1) { if (compare > 1) {
compare = Math.floor(compare); compare = Math.floor(compare);
return `${compare} hour${compare > 1 ? 's' : ''}`; return `${compare} hour${compare > 1 ? 's' : ''}`;
} }
compare = delta / oneMinute; compare = delta / oneMinute;
if (compare > 1) { if (compare > 1) {
compare = Math.floor(compare); compare = Math.floor(compare);
return `${compare} minute${compare > 1 ? 's' : ''}`; return `${compare} minute${compare > 1 ? 's' : ''}`;
} }
return 'less than a minute'; return 'less than a minute';
}; };

View File

@@ -1,12 +1,14 @@
import { SET_ACTIVE_EVENT, UNSET_ACTIVE_EVENT } from '../constants/actionTypes.js'; import { EVENTS_LOADED, SET_ACTIVE_EVENT, UNSET_ACTIVE_EVENT } from '../constants/actionTypes.js';
export const activeEvent = (state = null, action) => { export const activeEvent = (state = null, action) => {
switch (action.type) { switch (action.type) {
case SET_ACTIVE_EVENT: case EVENTS_LOADED:
return action.payload; return action.payload.size === 1 ? action.payload.get(0).get('id') : null;
case UNSET_ACTIVE_EVENT: case SET_ACTIVE_EVENT:
return null; return action.payload;
default: case UNSET_ACTIVE_EVENT:
return state; return null;
} default:
return state;
}
}; };

View File

@@ -1,12 +1,12 @@
import { SET_ACTIVE_ITEM, UNSET_ACTIVE_ITEM } from '../constants/actionTypes.js'; import { SET_ACTIVE_ITEM, UNSET_ACTIVE_ITEM } from '../constants/actionTypes.js';
export const activeItem = (state = null, action) => { export const activeItem = (state = null, action) => {
switch (action.type) { switch (action.type) {
case SET_ACTIVE_ITEM: case SET_ACTIVE_ITEM:
return action.payload; return action.payload;
case UNSET_ACTIVE_ITEM: case UNSET_ACTIVE_ITEM:
return null; return null;
default: default:
return state; return state;
} }
}; };

View File

@@ -2,10 +2,10 @@ import { SET_AUCTION_FILTER } from '../constants/actionTypes.js';
import { ITEM_FILTERS } from '../constants/constants.js'; import { ITEM_FILTERS } from '../constants/constants.js';
export const auctionFilter = (state = ITEM_FILTERS.ALL, action) => { export const auctionFilter = (state = ITEM_FILTERS.ALL, action) => {
switch (action.type) { switch (action.type) {
case SET_AUCTION_FILTER: case SET_AUCTION_FILTER:
return action.payload; return action.payload;
default: default:
return state; return state;
} }
}; };

View File

@@ -2,10 +2,10 @@ import { SET_AUCTION_VIEW_MODE } from '../constants/actionTypes.js';
import { AUCTION_VIEW_MODES } from '../constants/constants.js'; import { AUCTION_VIEW_MODES } from '../constants/constants.js';
export const auctionView = (state = AUCTION_VIEW_MODES.ALL, action) => { export const auctionView = (state = AUCTION_VIEW_MODES.ALL, action) => {
switch (action.type) { switch (action.type) {
case SET_AUCTION_VIEW_MODE: case SET_AUCTION_VIEW_MODE:
return action.payload; return action.payload;
default: default:
return state; return state;
} }
}; };

View File

@@ -3,16 +3,16 @@ import { Map } from 'immutable';
import { AUCTIONS_UPDATED, UPDATE_AUCTIONS } from '../constants/actionTypes.js'; import { AUCTIONS_UPDATED, UPDATE_AUCTIONS } from '../constants/actionTypes.js';
export const auctions = (state = new Map(), action) => { export const auctions = (state = new Map(), action) => {
switch (action.type) { switch (action.type) {
case AUCTIONS_UPDATED: case AUCTIONS_UPDATED:
return state.merge( return state.merge(
action.payload.toMap().mapEntries((entry) => { action.payload.toMap().mapEntries((entry) => {
const [, item] = entry; const [, item] = entry;
return [`${item.id}`, item]; return [`${item.id}`, item];
}), }),
); );
case UPDATE_AUCTIONS: case UPDATE_AUCTIONS:
default: default:
return state; return state;
} }
}; };

View File

@@ -1,14 +1,14 @@
import { LOGIN_SUCCESS, SET_AUTH, UNSET_AUTH } from '../constants/actionTypes.js'; import { LOGIN_SUCCESS, SET_AUTH, UNSET_AUTH } from '../constants/actionTypes.js';
export const auth = (state = null, action) => { export const auth = (state = null, action) => {
switch (action.type) { switch (action.type) {
case LOGIN_SUCCESS: case LOGIN_SUCCESS:
return action.payload.token; return action.payload.token;
case SET_AUTH: case SET_AUTH:
return action.payload; return action.payload;
case UNSET_AUTH: case UNSET_AUTH:
return null; return null;
default: default:
return state; return state;
} }
}; };

View File

@@ -1,12 +1,12 @@
import { BLOCK_UI, UNBLOCK_UI } from '../constants/actionTypes.js'; import { BLOCK_UI, UNBLOCK_UI } from '../constants/actionTypes.js';
export const blockUI = (state = false, action) => { export const blockUI = (state = false, action) => {
switch (action.type) { switch (action.type) {
case BLOCK_UI: case BLOCK_UI:
return true; return true;
case UNBLOCK_UI: case UNBLOCK_UI:
return false; return false;
default: default:
return state; return state;
} }
}; };

View File

@@ -3,15 +3,15 @@ import { Map } from 'immutable';
import { EVENTS_LOADED, GET_EVENTS } from '../constants/actionTypes.js'; import { EVENTS_LOADED, GET_EVENTS } from '../constants/actionTypes.js';
export const events = (state = new Map(), action) => { export const events = (state = new Map(), action) => {
switch (action.type) { switch (action.type) {
case EVENTS_LOADED: case EVENTS_LOADED:
const mapped = action.payload.toMap().mapEntries((entry) => { const mapped = action.payload.toMap().mapEntries((entry) => {
const [, event] = entry; const [, event] = entry;
return [`${event.id}`, event]; return [`${event.id}`, event];
}); });
return state.merge(mapped); return state.merge(mapped);
case GET_EVENTS: case GET_EVENTS:
default: default:
return state; return state;
} }
}; };

View File

@@ -12,16 +12,16 @@ import { items } from './items.js';
import { profile } from './profile.js'; import { profile } from './profile.js';
const rootReducer = combineReducers({ const rootReducer = combineReducers({
activeEvent, activeEvent,
activeItem, activeItem,
auctionFilter, auctionFilter,
auctions, auctions,
auctionView, auctionView,
auth, auth,
blockUI, blockUI,
events, events,
items, items,
profile, profile,
}); });
export default rootReducer; export default rootReducer;

View File

@@ -1,20 +1,17 @@
import { Map } from 'immutable'; import { Map } from 'immutable';
import { import { GET_ITEMS, ITEMS_LOADED } from '../constants/actionTypes.js';
GET_ITEMS,
ITEMS_LOADED,
} from '../constants/actionTypes.js';
export const items = (state = new Map(), action) => { export const items = (state = new Map(), action) => {
switch (action.type) { switch (action.type) {
case ITEMS_LOADED: case ITEMS_LOADED:
const mapped = action.payload.toMap().mapEntries((entry) => { const mapped = action.payload.toMap().mapEntries((entry) => {
const [, item] = entry; const [, item] = entry;
return [`${item.id}`, item]; return [`${item.id}`, item];
}); });
return state.merge(mapped); return state.merge(mapped);
case GET_ITEMS: case GET_ITEMS:
default: default:
return state; return state;
} }
}; };

View File

@@ -1,23 +1,23 @@
import { Map } from 'immutable'; import { Map } from 'immutable';
import { import {
LOGIN_SUCCESS, LOGIN_SUCCESS,
SET_PROFILE, SET_PROFILE,
UNSET_PROFILE, UNSET_PROFILE,
UPDATE_PROFILE, UPDATE_PROFILE,
} from '../constants/actionTypes.js'; } from '../constants/actionTypes.js';
export const profile = (state = new Map(), action) => { export const profile = (state = new Map(), action) => {
switch (action.type) { switch (action.type) {
case LOGIN_SUCCESS: case LOGIN_SUCCESS:
return action.payload.user; return action.payload.user;
case SET_PROFILE: case SET_PROFILE:
return action.payload; return action.payload;
case UNSET_PROFILE: case UNSET_PROFILE:
return new Map(); return new Map();
case UPDATE_PROFILE: case UPDATE_PROFILE:
return action.payload; return action.payload;
default: default:
return state; return state;
} }
}; };

View File

@@ -16,155 +16,177 @@ import Profile from './screens/Profile.container.js';
import Register from './screens/Register.js'; import Register from './screens/Register.js';
import SignInOrRegister from './screens/SignInOrRegister.js'; import SignInOrRegister from './screens/SignInOrRegister.js';
export const Tabs = createBottomTabNavigator({
'Event': {
screen: Event,
navigationOptions: {
tabBarLabel: 'Event',
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="font-awesome" size={28} color={tintColor} />,
},
},
'Bazaar': {
screen: Marketplace,
navigationOptions: {
tabBarLabel: 'Bazaar',
tabBarIcon: ({ tintColor }) => <Icon name="store" type="fontisto" size={28} color={tintColor} />,
},
},
'Profile': {
screen: Profile,
navigationOptions: {
tabBarLabel: 'Profile',
tabBarIcon: ({ tintColor }) => <Icon name="ios-person" type="font-awesome" size={28} color={tintColor} />,
},
},
});
export const SignInOrRegisterStack = createStackNavigator({ export const SignInOrRegisterStack = createStackNavigator({
SignInOrRegister: { SignInOrRegister: {
screen: SignInOrRegister, screen: SignInOrRegister,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: null, header: null,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
}, },
Register: { Register: {
screen: Register, screen: Register,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: null, header: null,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
}, },
}); });
export const AuctionStack = createStackNavigator({ export const AuctionStack = createStackNavigator({
Auction: { Auction: {
screen: Auction, screen: Auction,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: <AppHeader navigation={navigation} />, header: <AppHeader navigation={navigation} />,
}), }),
}, },
Item: { Item: {
screen: Item, screen: Item,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: null, header: null,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
}, },
ImageDetail: { ImageDetail: {
screen: ImageDetail, screen: ImageDetail,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: null, header: null,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
}, },
}); });
export const BazaarStack = createStackNavigator({ export const BazaarStack = createStackNavigator({
Bazaar: { Bazaar: {
screen: Marketplace, screen: Marketplace,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: <AppHeader navigation={navigation} />, header: <AppHeader navigation={navigation} />,
}), }),
}, },
Item: { Item: {
screen: Item, screen: Item,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: null, header: null,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
}, },
ImageDetail: { ImageDetail: {
screen: ImageDetail, screen: ImageDetail,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: null, header: null,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
}, },
Checkout: { Checkout: {
screen: Checkout, screen: Checkout,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: null, header: null,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
}, },
}); });
export const EventsStack = createStackNavigator({ export const EventsStack = createStackNavigator({
Events: { Events: {
screen: Events, screen: Events,
navigationOptions: ({ navigation }) => ({ navigationOptions: ({ navigation }) => ({
header: <AppHeader navigation={navigation} />, header: <AppHeader navigation={navigation} />,
tabBarVisible: false, tabBarVisible: false,
gesturesEnabled: false, gesturesEnabled: false,
}), }),
} },
Event: {
screen: Event,
navigationOptions: ({ navigation }) => ({
header: <AppHeader navigation={navigation} />,
tabBarVisible: false,
gesturesEnabled: false,
}),
},
});
export const Tabs = createBottomTabNavigator({
Event: {
screen: EventsStack,
navigationOptions: {
tabBarLabel: 'Event',
tabBarIcon: ({ tintColor }) => (
<Icon name="black-tie" type="font-awesome" size={28} color={tintColor} />
),
},
},
Auction: {
screen: AuctionStack,
navigationOptions: {
tabBarLabel: 'Silent Auction',
tabBarIcon: ({ tintColor }) => (
<Icon name="gavel" type="font-awesome" size={28} color={tintColor} />
),
},
},
Bazaar: {
screen: BazaarStack,
navigationOptions: {
tabBarLabel: 'Bazaar',
tabBarIcon: ({ tintColor }) => (
<Icon name="store" type="fontisto" size={28} color={tintColor} />
),
},
},
Profile: {
screen: Profile,
navigationOptions: {
tabBarLabel: 'Profile',
tabBarIcon: ({ tintColor }) => (
<Icon name="ios-person" type="font-awesome" size={28} color={tintColor} />
),
},
},
}); });
export const createRootNavigator = () => { export const createRootNavigator = () => {
return StackNavigator( return StackNavigator(
{ {
AuctionStack: { AuctionStack: {
screen: AuctionStack, screen: AuctionStack,
navigationOptions: { navigationOptions: {
gesturesEnabled: false, gesturesEnabled: false,
} },
}, },
BazaarStack: { BazaarStack: {
screen: BazaarStack, screen: BazaarStack,
navigationOptions: { navigationOptions: {
gesturesEnabled: false, gesturesEnabled: false,
} },
}, },
EventsStack: { EventsStack: {
screen: EventsStack, screen: EventsStack,
navigationOptions: { navigationOptions: {
gesturesEnabled: false, gesturesEnabled: false,
} },
}, },
Tabs: { SignInOrRegisterStack: {
screen: Tabs, screen: SignInOrRegister,
navigationOptions: { navigationOptions: {
gesturesEnabled: false, gesturesEnabled: false,
} },
} },
}, Tabs: {
{ screen: Tabs,
mode: "modal", navigationOptions: {
} gesturesEnabled: false,
); },
},
},
{
mode: 'modal',
},
);
}; };

View File

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

View File

@@ -2,11 +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 { import { FlatList, Text, View } from 'react-native';
FlatList,
Text,
View,
} from 'react-native';
import { SORT_MODES, AUCTION_VIEW_MODES } from '../constants/constants.js'; import { SORT_MODES, AUCTION_VIEW_MODES } from '../constants/constants.js';
@@ -16,70 +12,74 @@ import AuctionListItem from '../containers/Auction/AuctionListItem.js';
import styles from './Auction.styles.js'; import styles from './Auction.styles.js';
export default class Auction extends Component { export default class Auction extends Component {
static get propTypes() { static get propTypes() {
return { return {
changeFilter: PropTypes.func, changeFilter: PropTypes.func,
changeViewMode: PropTypes.func.isRequired, changeViewMode: PropTypes.func.isRequired,
fetchItems: PropTypes.func.isRequired, fetchItems: PropTypes.func.isRequired,
fetchStatus: PropTypes.func.isRequired, fetchStatus: PropTypes.func.isRequired,
items: PropTypes.oneOfType([ items: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(List)]),
PropTypes.array, };
PropTypes.instanceOf(List), }
]),
};
}
static get defaultProps() { static get defaultProps() {
return { return {
changeFilter: () => { console.log('Change Filter Default Prop', arguments); }, changeFilter: () => {
items: [], console.log('Change Filter Default Prop', arguments);
}; },
} items: [],
};
}
constructor(props) { constructor(props) {
super(props); super(props);
this.changeFilter = this.changeFilter.bind(this); this.changeFilter = this.changeFilter.bind(this);
this.changeViewMode = this.changeViewMode.bind(this); this.changeViewMode = this.changeViewMode.bind(this);
this.state = { this.state = {
sort: SORT_MODES.TITLE_ASC, sort: SORT_MODES.TITLE_ASC,
view: AUCTION_VIEW_MODES.ALL, view: AUCTION_VIEW_MODES.ALL,
}; };
} }
componentDidMount() { componentDidMount() {
this.props.fetchStatus(); this.props.fetchStatus();
this.props.fetchItems(); this.props.fetchItems();
} }
changeFilter(filter) { changeFilter(filter) {
this.props.changeFilter('auction', filter); this.props.changeFilter('auction', filter);
} }
_keyExtractor = (item, index) => `${item._id}_${index}`; changeViewMode(viewMode) {
this.props.changeViewMode(viewMode);
}
_renderAuctionListItem = ({ item }) => <AuctionListItem item={item} />; _keyExtractor = (item, index) => `${item.id}_${index}`;
render() { _renderAuctionListItem = ({ item }) => <AuctionListItem item={item} />;
const { items } = this.props;
const { sort, view } = this.state;
return ( render() {
<View style={styles.container}> const { items } = this.props;
<FilterBar const { sort, view } = this.state;
changeFilterer={this.changeFilter}
/> return (
{items.size > 0 && ( <View style={styles.container}>
<FlatList <FilterBar
data={items} changeFilterer={this.changeFilter}
keyExtractor={this._keyExtractor} changeViewMode={this.changeViewMode}
renderItem={this._renderAuctionListItem} />
contentContainerStyle={styles.itemListContentContainer} {items.size > 0 && (
style={styles.itemList} <FlatList
/> data={items}
)} keyExtractor={this._keyExtractor}
</View> renderItem={this._renderAuctionListItem}
); contentContainerStyle={styles.itemListContentContainer}
} style={styles.itemList}
/>
)}
</View>
);
}
} }

View File

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

View File

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

View File

@@ -7,30 +7,33 @@ import { getActiveEvent, getDefaultEvent } from '../selectors/events.js';
import Event from './Event.js'; import Event from './Event.js';
const matchStateToProps = (state) => { const matchStateToProps = (state) => {
const event = getActiveEvent(state) || getDefaultEvent(state) || new EventRecord(); const event = getActiveEvent(state) || getDefaultEvent(state) || new EventRecord();
if (!event) { if (!event) {
return {}; return {};
} }
return { return {
description: event.get('description'), description: event.get('description'),
endTime: event.get('endTime'), endTime: event.get('endTime'),
id: event.get('id'), id: event.get('id'),
images: event.get('images').toArray(), images: event.get('images').toArray(),
isTicketed: event.get('isTicketed'), isTicketed: event.get('isTicketed'),
posts: event.get('posts').toArray(), posts: event.get('posts').toArray(),
requireLoginToSeeAuction: event.get('requireLoginToSeeAuction'), requireLoginToSeeAuction: event.get('requireLoginToSeeAuction'),
showFrom: event.get('showFrom'), showFrom: event.get('showFrom'),
showUntil: event.get('showUntil'), showUntil: event.get('showUntil'),
startTime: event.get('startTime'), startTime: event.get('startTime'),
tagline: event.get('tagline'), tagline: event.get('tagline'),
ticketClasses: event.get('ticketClasses').toArray(), ticketClasses: event.get('ticketClasses').toArray(),
title: event.get('title'), title: event.get('title'),
url: event.get('url'), url: event.get('url'),
}; };
}; };
const mapDispatchToProps = (dispatch) => ({}); const mapDispatchToProps = (dispatch) => ({});
export default connect(matchStateToProps, mapDispatchToProps)(Event); export default connect(
matchStateToProps,
mapDispatchToProps,
)(Event);

View File

@@ -5,82 +5,76 @@ import { Text, View } from 'react-native';
import styles from './Event.styles.js'; import styles from './Event.styles.js';
export default class Event extends Component { export default class Event extends Component {
static get propTypes() { static get propTypes() {
return { return {
description: PropTypes.string, description: PropTypes.string,
endTime: PropTypes.string, endTime: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
images: PropTypes.arrayOf( images: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
url: PropTypes.string, url: PropTypes.string,
}), }),
), ),
isTicketed: PropTypes.bool, isTicketed: PropTypes.bool,
posts: PropTypes.arrayOf( posts: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
author: PropTypes.string, author: PropTypes.string,
content: PropTypes.string, content: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
isPublic: PropTypes.bool, isPublic: PropTypes.bool,
scheduledPost: PropTypes.bool, scheduledPost: PropTypes.bool,
sendNotification: PropTypes.bool, sendNotification: PropTypes.bool,
timestamp: PropTypes.string, timestamp: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
}), }),
), ),
requireLoginToSeeAuction: PropTypes.bool, requireLoginToSeeAuction: PropTypes.bool,
showFrom: PropTypes.string, showFrom: PropTypes.string,
showUntil: PropTypes.string, showUntil: PropTypes.string,
startTime: PropTypes.string, startTime: PropTypes.string,
tagline: PropTypes.string, tagline: PropTypes.string,
ticketClasses: PropTypes.arrayOf( ticketClasses: PropTypes.arrayOf(PropTypes.shape({})),
PropTypes.shape({ title: PropTypes.string,
url: PropTypes.string,
};
}
}), static get defaultProps() {
), return {
title: PropTypes.string, images: null,
url: PropTypes.string, isTicketed: false,
}; posts: null,
} requireLoginToSeeAuction: false,
tagline: null,
ticketClasses: null,
url: null,
};
}
static get defaultProps() { constructor(props) {
return { super(props);
images: null, }
isTicketed: false,
posts: null,
requireLoginToSeeAuction: false,
tagline: null,
ticketClasses: null,
url: null,
};
}
constructor(props) { render() {
super(props); const {
} description,
endTime,
images,
isTicketed,
requireLoginToSeeAuction,
showFrom,
showUntil,
startTime,
tagline,
ticketClasses,
title,
url,
} = this.props;
render() { return (
const { <View style={styles.container}>
description, <Text style={styles.title}>Event</Text>
endTime, </View>
images, );
isTicketed, }
requireLoginToSeeAuction,
showFrom,
showUntil,
startTime,
tagline,
ticketClasses,
title,
url,
} = this.props;
return (
<View style={styles.container}>
<Text style={styles.title}>
Event
</Text>
</View>
);
}
} }

View File

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

View File

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

View File

@@ -1,58 +1,71 @@
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 { import { FlatList, StyleSheet, Text, View } from 'react-native';
StyleSheet,
Text, import EventListItem from '../components/Events/EventListItem.js';
View,
} from 'react-native';
export default class Events extends Component { export default class Events extends Component {
static get propTypes() { static get propTypes() {
return { return {
events: PropTypes.array.isRequired, events: PropTypes.instanceOf(List),
fetchEvents: PropTypes.func.isRequired, fetchEvents: PropTypes.func.isRequired,
}; setActiveEvent: PropTypes.func.isRequired,
} };
}
constructor(props) { static get defaultProps() {
super(props); return {
} events: new List(),
};
}
componentDidMount() { constructor(props) {
this.props.fetchEvents(); super(props);
}
_keyExtractor = (event, index) => `${event._id}_${index}`; this._setActiveEvent = this.setActiveEvent.bind(this);
}
_renderEventListItem = ({ event }) => <EventListItem event={event} />; componentDidMount() {
this.props.fetchEvents();
}
render() { setActiveEvent(eventId) {
const { events } = this.props; this.props.setActiveEvent(eventId);
}
return ( _keyExtractor = (event, index) => `${event.id}_${index}`;
<View style={styles.container}>
{events.size > 0 && ( _renderEventListItem = ({ event }) => (
<FlatList <EventListItem {...event} setActiveEvent={this.setActiveEvent} />
data={events}
keyExtractor={this._keyExtractor}
renderItem={this._renderEventListItem}
contentContainerStyle={styles.eventListContentContainer}
style={styles.eventList}
/>
)}
</View>
); );
}
render() {
const { events } = this.props;
return (
<View style={styles.container}>
{events.size > 0 && (
<FlatList
data={events}
keyExtractor={this._keyExtractor}
renderItem={this._renderEventListItem}
contentContainerStyle={styles.eventListContentContainer}
style={styles.eventList}
/>
)}
</View>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
backgroundColor: '#F5FCFF', backgroundColor: '#F5FCFF',
}, },
eventListContentContainer: { eventListContentContainer: {},
},
}); });

View File

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

View File

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

View File

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

View File

@@ -2,11 +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 { import { FlatList, Text, View } from 'react-native';
FlatList,
Text,
View,
} from 'react-native';
import { SORT_MODES, AUCTION_VIEW_MODES } from '../constants/constants.js'; import { SORT_MODES, AUCTION_VIEW_MODES } from '../constants/constants.js';
@@ -16,70 +12,67 @@ import AuctionListItem from '../containers/Auction/AuctionListItem.js';
//import styles from './Marketplace.styles.js'; //import styles from './Marketplace.styles.js';
export default class Marketplace extends Component { export default class Marketplace extends Component {
static get propTypes() { static get propTypes() {
return { return {
changeFilter: PropTypes.func, changeFilter: PropTypes.func,
changeViewMode: PropTypes.func.isRequired, changeViewMode: PropTypes.func.isRequired,
fetchItems: PropTypes.func.isRequired, fetchItems: PropTypes.func.isRequired,
fetchStatus: PropTypes.func.isRequired, fetchStatus: PropTypes.func.isRequired,
items: PropTypes.oneOfType([ items: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(List)]),
PropTypes.array, };
PropTypes.instanceOf(List), }
]),
};
}
static get defaultProps() { static get defaultProps() {
return { return {
changeFilter: () => { console.log('Change Filter Default Prop', arguments); }, changeFilter: () => {
items: [], console.log('Change Filter Default Prop', arguments);
}; },
} items: [],
};
}
constructor(props) { constructor(props) {
super(props); super(props);
this.changeFilter = this.changeFilter.bind(this); this.changeFilter = this.changeFilter.bind(this);
this.changeViewMode = this.changeViewMode.bind(this); this.changeViewMode = this.changeViewMode.bind(this);
this.state = { this.state = {
sort: SORT_MODES.TITLE_ASC, sort: SORT_MODES.TITLE_ASC,
view: AUCTION_VIEW_MODES.ALL, view: AUCTION_VIEW_MODES.ALL,
}; };
} }
componentDidMount() { componentDidMount() {
this.props.fetchStatus(); this.props.fetchStatus();
this.props.fetchItems(); this.props.fetchItems();
} }
changeFilter(filter) { changeFilter(filter) {
this.props.changeFilter('auction', filter); this.props.changeFilter('auction', filter);
} }
_keyExtractor = (item, index) => `${item._id}_${index}`; _keyExtractor = (item, index) => `${item._id}_${index}`;
_renderAuctionListItem = ({ item }) => <AuctionListItem item={item} />; _renderAuctionListItem = ({ item }) => <AuctionListItem item={item} />;
render() { render() {
const { items } = this.props; const { items } = this.props;
const { sort, view } = this.state; const { sort, view } = this.state;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<FilterBar <FilterBar changeFilterer={this.changeFilter} />
changeFilterer={this.changeFilter} {items.size > 0 && (
/> <FlatList
{items.size > 0 && ( data={items}
<FlatList keyExtractor={this._keyExtractor}
data={items} renderItem={this._renderAuctionListItem}
keyExtractor={this._keyExtractor} contentContainerStyle={styles.itemListContentContainer}
renderItem={this._renderAuctionListItem} style={styles.itemList}
contentContainerStyle={styles.itemListContentContainer} />
style={styles.itemList} )}
/> </View>
)} );
</View> }
);
}
} }

View File

@@ -6,27 +6,30 @@ import { getNomDeBid, getProfile, isAllowedToBid } from '../selectors/profile.js
import Profile from './Profile.js'; import Profile from './Profile.js';
const matchStateToProps = (state) => { const matchStateToProps = (state) => {
const profile = getProfile(state); const profile = getProfile(state);
return { return {
hasLinkedApple: profile.get('hasLinkedApple'), hasLinkedApple: profile.get('hasLinkedApple'),
hasLinkedFacebook: profile.get('hasLinkedFacebook'), hasLinkedFacebook: profile.get('hasLinkedFacebook'),
hasLinkedGoogle: profile.get('hasLinkedGoogle'), hasLinkedGoogle: profile.get('hasLinkedGoogle'),
hasLocalAccount: profile.get('hasLocalAccount'), hasLocalAccount: profile.get('hasLocalAccount'),
hasRegisteredAcccount: profile.get('hasRegisteredAcccount'), hasRegisteredAcccount: profile.get('hasRegisteredAcccount'),
id: profile.get('id'), id: profile.get('id'),
isAllowedToBid: isAllowedToBid(state), isAllowedToBid: isAllowedToBid(state),
isVerified: profile.get('isVerified'), isVerified: profile.get('isVerified'),
lastName: profile.get('lastName'), lastName: profile.get('lastName'),
nomDeBid: getNomDeBid(state), nomDeBid: getNomDeBid(state),
organizationIdentifier: profile.get('organizationIdentifier'), organizationIdentifier: profile.get('organizationIdentifier'),
paymentToken: profile.get('paymentToken'), paymentToken: profile.get('paymentToken'),
}; };
}; };
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
fetchProfile: () => dispatch(fetchProfile(dispatch)), fetchProfile: () => dispatch(fetchProfile(dispatch)),
updateProfile: () => dispatch(updateProfile(dispatch)), updateProfile: () => dispatch(updateProfile(dispatch)),
}); });
export default connect(matchStateToProps, mapDispatchToProps)(Profile); export default connect(
matchStateToProps,
mapDispatchToProps,
)(Profile);

View File

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

View File

@@ -1,17 +1,16 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { checkEmailAvailability, checkNomAvailability, registerUser } from '../actions/profile.js';
checkEmailAvailability,
checkNomAvailability,
registerUser,
} from '../actions/profile.js';
import Register from './Register.js'; import Register from './Register.js';
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
checkEmail: (email) => dispatch(checkEmailAvailability(email)), checkEmail: (email) => dispatch(checkEmailAvailability(email)),
checkNomDeBid: (nomDeBid) => dispatch(checkNomAvailability(nomDeBid)), checkNomDeBid: (nomDeBid) => dispatch(checkNomAvailability(nomDeBid)),
doRegistration: (user) => dispatch(registerUser(user)), doRegistration: (user) => dispatch(registerUser(user)),
}); });
export default connect(null, mapDispatchToProps)(Register); export default connect(
null,
mapDispatchToProps,
)(Register);

View File

@@ -9,72 +9,80 @@ const STRINGS = {
}; };
export default class Register extends Component { export default class Register extends Component {
static get propTypes() {
static get propTypes() { return {
return { checkEmail: PropTypes.func.isRequired,
checkEmail: PropTypes.func.isRequired, checkNomDeBid: PropTypes.func.isRequired,
checkNomDeBid: PropTypes.func.isRequired, doRegistration: PropTypes.func.isRequired,
doRegistration: PropTypes.func.isRequired, // invalidEmail: PropTypes.bool.isRequired,
// invalidEmail: PropTypes.bool.isRequired, // invalidNomDeBid: PropTypes.bool.isRequired,
// invalidNomDeBid: PropTypes.bool.isRequired, };
};
}
constructor() {
super(props);
this.state = {
addresses: [],
avatar: null,
email: null,
firstName: null,
lastName: null,
nomDeBid: null,
invalidEmail: false,
invalidNomDeBid: false,
password: null,
phones: [],
};
this._doRegistration = this._doRegistration.bind(this);
}
_doRegistration() {
if (!this.isFormComplete()) {
console.error('Incomplete form... how did the button become enabled?');
alert('Please complete all of the required fields. They have bold labels.');
return;
} }
this.props.doRegistration(this.getUserRegistration()); constructor() {
} super(props);
_validateEmail() { this.state = {
this.props.checkEmail(this.state.email, (valid) => this.setState('invalidEmail', valid)); addresses: [],
} avatar: null,
email: null,
firstName: null,
lastName: null,
nomDeBid: null,
invalidEmail: false,
invalidNomDeBid: false,
password: null,
phones: [],
};
_validateNomDeBid() { this._doRegistration = this._doRegistration.bind(this);
this.props.checkNomDeBid(this.state.nomDeBid, (valid) => this.setState('invalidNomDeBid', valid)); }
}
getUserRegistration() { _doRegistration() {
return { if (!this.isFormComplete()) {
addresses: this.state.addresses, console.error('Incomplete form... how did the button become enabled?');
avatar: this.state.avatar, alert('Please complete all of the required fields. They have bold labels.');
email: this.state.email, return;
firstName: this.state.firstName, }
lastName: this.state.lastName,
nomDeBid: this.state.nomDeBid,
password: this.state.password,
phones: this.state.phones,
};
}
isFormComplete() { this.props.doRegistration(this.getUserRegistration());
return !this.state.invalidEmail && !this.state.invalidNomDeBid && }
!!this.state.firstName && !!this.state.lastName && !!this.state.email &&
!!this.state.nomDeBid && !!this.state.phones.length && !!this.state.password; _validateEmail() {
} this.props.checkEmail(this.state.email, (valid) => this.setState('invalidEmail', valid));
}
_validateNomDeBid() {
this.props.checkNomDeBid(this.state.nomDeBid, (valid) =>
this.setState('invalidNomDeBid', valid),
);
}
getUserRegistration() {
return {
addresses: this.state.addresses,
avatar: this.state.avatar,
email: this.state.email,
firstName: this.state.firstName,
lastName: this.state.lastName,
nomDeBid: this.state.nomDeBid,
password: this.state.password,
phones: this.state.phones,
};
}
isFormComplete() {
return (
!this.state.invalidEmail &&
!this.state.invalidNomDeBid &&
!!this.state.firstName &&
!!this.state.lastName &&
!!this.state.email &&
!!this.state.nomDeBid &&
!!this.state.phones.length &&
!!this.state.password
);
}
render() { render() {
return ( return (

View File

@@ -1,16 +1,12 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({ export default (styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
}, },
title: { title: {},
}, localLogin: {},
localLogin: { services: {},
}, register: {},
services: { }));
},
register: {
},
});

View File

@@ -8,31 +8,30 @@ import LocalLogin from '../components/Login/LocalLogin.container.js';
import styles from './SignInOrRegister.styles.js'; import styles from './SignInOrRegister.styles.js';
export default class SignInOrRegister extends Component { export default class SignInOrRegister extends Component {
constructor() {
super(props);
constructor() { this._doRegistration = this._doRegistration.bind(this);
super(props); }
this._doRegistration = this._doRegistration.bind(this); _doRegistration() {
} this.props.navigation.navigate('Register');
}
_doRegistration() { render() {
this.props.navigation.navigate('Register'); return (
} <View style={styles.container}>
<Text style={styles.title}>Sign In or Register</Text>
render() { <View style={styles.localLogin}>
return ( <LocalLogin />
<View style={styles.container}> </View>
<Text style={styles.title}>Sign In or Register</Text> <View style={styles.services}>
<View style={styles.localLogin}> <FacebookLogin />
<LocalLogin /> </View>
</View> <View style={styles.register}>
<View style={styles.services}> <Button title="Signup with Email" onPress={this._doRegistration} />
<FacebookLogin /> </View>
</View> </View>
<View style={styles.register}> );
<Button title="Signup with Email" onPress={this._doRegistration} /> }
</View>
</View>
);
}
} }

View File

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

View File

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

View File

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

View File

@@ -6,28 +6,30 @@ export const getItemBidCount = (state, itemId) => state.getIn(['auctions', itemI
export const getItemPrice = (state, itemId) => state.getIn(['auctions', itemId, 'currentPrice'], 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 isBiddingItem = (state, itemId) =>
state.getIn(['auctions', itemId, 'isBidding'], false);
export const isWinningItem = (state, itemId) => state.getIn(['auctions', itemId, 'isWinning'], false); export const isWinningItem = (state, itemId) =>
state.getIn(['auctions', itemId, 'isWinning'], false);
export const getAuctionStatus = (state, itemId) => state.getIn(['actions', itemId], false); export const getAuctionStatus = (state, itemId) => state.getIn(['actions', itemId], false);
export const getAuctionStatuses = createSelector( export const getAuctionStatuses = createSelector(
[getState], [getState],
(state) => state.get('actions') || new Map(), (state) => state.get('actions') || new Map(),
); );
export const getItemsIdsWithNoBids = createSelector( export const getItemsIdsWithNoBids = createSelector(
[getAuctionStatuses], [getAuctionStatuses],
(auctions) => auctions.filter(auction => auction.bidCount === 0), (auctions) => auctions.filter((auction) => auction.bidCount === 0),
); );
export const getMyBidItemIds = createSelector( export const getMyBidItemIds = createSelector(
[getAuctionStatuses], [getAuctionStatuses],
(auctions) => auctions.filter(auction => auction.isBidding), (auctions) => auctions.filter((auction) => auction.isBidding),
); );
export const getMyWinningItemIds = createSelector( export const getMyWinningItemIds = createSelector(
[getAuctionStatuses], [getAuctionStatuses],
(auctions) => auctions.filter(auction => auction.isWinning), (auctions) => auctions.filter((auction) => auction.isWinning),
); );

View File

@@ -3,6 +3,6 @@ import { createSelector } from 'reselect';
const getState = (state) => state; const getState = (state) => state;
export const getAuthToken = createSelector( export const getAuthToken = createSelector(
[getState], [getState],
(state) => state.get('auth'), (state) => state.get('auth'),
); );

View File

@@ -8,26 +8,26 @@ const getState = (state) => state;
export const getEventById = (state, eventId) => state.getIn(['events', eventId], false); export const getEventById = (state, eventId) => state.getIn(['events', eventId], false);
export const getEvents = createSelector( export const getEvents = createSelector(
[getState], [getState],
(state) => state.get('events') || new Map(), (state) => state.get('events') || new Map(),
); );
export const getActiveEvent = createSelector( export const getActiveEvent = createSelector(
[getActiveEventId, getEvents], [getActiveEventId, getEvents],
(eventId, eventsAsMap) => eventId ? eventsAsMap.get(eventId) : null, (eventId, eventsAsMap) => (eventId ? eventsAsMap.get(eventId) : null),
); );
export const getDefaultEvent = createSelector( export const getDefaultEvent = createSelector(
[getEvents], [getEvents],
(eventsAsMap) => eventsAsMap.first(), (eventsAsMap) => eventsAsMap.first(),
); );
export const getEventsAsList = createSelector( export const getEventsAsList = createSelector(
[getEvents], [getEvents],
(eventsAsMap) => eventsAsMap.toList(), (eventsAsMap) => eventsAsMap.toList(),
); );
export const hasMultipleEvents = createSelector( export const hasMultipleEvents = createSelector(
[getEvents], [getEvents],
(eventsAsMap) => eventsAsMap.size > 1, (eventsAsMap) => eventsAsMap.size > 1,
); );

View File

@@ -8,61 +8,61 @@ const getState = (state) => state;
export const getItem = (state, itemId) => state.getIn(['items', itemId], false); export const getItem = (state, itemId) => state.getIn(['items', itemId], false);
export const getItems = createSelector( export const getItems = createSelector(
[getState], [getState],
(state) => state.get('items') || new Map(), (state) => state.get('items') || new Map(),
); );
export const getItemsAsList = createSelector( export const getItemsAsList = createSelector(
[getItems], [getItems],
(itemsAsMap) => itemsAsMap.toList(), (itemsAsMap) => itemsAsMap.toList(),
); );
export const getAuctionItems = createSelector( export const getAuctionItems = createSelector(
[getState], [getState],
(state) => state.get('items').filter(i => i.type === 'auction') || new Map(), (state) => state.get('items').filter((i) => i.type === 'auction') || new Map(),
); );
export const getAuctionItemsAsList = createSelector( export const getAuctionItemsAsList = createSelector(
[getAuctionItems], [getAuctionItems],
(auctionItemsAsMap) => auctionItemsAsMap.toList(), (auctionItemsAsMap) => auctionItemsAsMap.toList(),
); );
export const getAuctionItemsUserIsBidding = createSelector( export const getAuctionItemsUserIsBidding = createSelector(
[getAuctionItems, getMyBidItemIds], [getAuctionItems, getMyBidItemIds],
(items, myBids) => items.filter(i => myBids.indexOf(i.id)) || new Map(), (items, myBids) => items.filter((i) => myBids.indexOf(i.id)) || new Map(),
); );
export const getAuctionItemsUserIsBiddingAsList = createSelector( export const getAuctionItemsUserIsBiddingAsList = createSelector(
[getAuctionItemsUserIsBidding], [getAuctionItemsUserIsBidding],
(auctionItemsAsMap) => auctionItemsAsMap.toList(), (auctionItemsAsMap) => auctionItemsAsMap.toList(),
); );
export const getAuctionItemsUserIsWinning = createSelector( export const getAuctionItemsUserIsWinning = createSelector(
[getAuctionItemsUserIsBidding, getMyWinningItemIds], [getAuctionItemsUserIsBidding, getMyWinningItemIds],
(items, myWins) => items.filter(i => myWins.indexOf(i.id)) || new Map(), (items, myWins) => items.filter((i) => myWins.indexOf(i.id)) || new Map(),
); );
export const getAuctionItemsUserIsWinningAsList = createSelector( export const getAuctionItemsUserIsWinningAsList = createSelector(
[getAuctionItemsUserIsWinning], [getAuctionItemsUserIsWinning],
(auctionItemsAsMap) => auctionItemsAsMap.toList(), (auctionItemsAsMap) => auctionItemsAsMap.toList(),
); );
export const getAuctionItemsWithNoBids = createSelector( export const getAuctionItemsWithNoBids = createSelector(
[getAuctionItems, getItemsIdsWithNoBids], [getAuctionItems, getItemsIdsWithNoBids],
(items, noBids) => items.filter(i => noBids.indexOf(i.id)) || new Map(), (items, noBids) => items.filter((i) => noBids.indexOf(i.id)) || new Map(),
); );
export const getAuctionItemsWithNoBidsAsList = createSelector( export const getAuctionItemsWithNoBidsAsList = createSelector(
[getAuctionItemsWithNoBids], [getAuctionItemsWithNoBids],
(auctionItemsAsMap) => auctionItemsAsMap.toList(), (auctionItemsAsMap) => auctionItemsAsMap.toList(),
); );
export const getTicketItems = createSelector( export const getTicketItems = createSelector(
[getState], [getState],
(state) => state.get('items').filter(i => i.type === 'ticket') || new Map(), (state) => state.get('items').filter((i) => i.type === 'ticket') || new Map(),
); );
export const getTicketItemsAsList = createSelector( export const getTicketItemsAsList = createSelector(
[getTicketItems], [getTicketItems],
(ticketItemsAsMap) => ticketItemsAsMap.toList(), (ticketItemsAsMap) => ticketItemsAsMap.toList(),
); );

View File

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

View File

@@ -5,10 +5,10 @@ import thunk from 'redux-thunk';
import rootReducer from '../reducers/index.js'; import rootReducer from '../reducers/index.js';
const composeEnhancers = composeWithDevTools({ port: 8000, realtime: true, suppressConnectErrors: false }); const composeEnhancers = composeWithDevTools({
port: 8000,
realtime: true,
suppressConnectErrors: false,
});
export const store = createStore( export const store = createStore(rootReducer, Map(), composeEnhancers(applyMiddleware(thunk)));
rootReducer,
Map(),
composeEnhancers(applyMiddleware(thunk)),
);

View File

@@ -1,3 +1,3 @@
module.exports = { module.exports = {
presets: ['module:metro-react-native-babel-preset'], presets: ['module:metro-react-native-babel-preset'],
}; };

View File

@@ -11,11 +11,11 @@ import { name as appName } from './app.json';
import { store } from './app/store/index.js'; import { store } from './app/store/index.js';
const connectedApp = () => { const connectedApp = () => {
return ( return (
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider> </Provider>
); );
}; };
AppRegistry.registerComponent(appName, () => connectedApp); AppRegistry.registerComponent(appName, () => connectedApp);

View File

@@ -11,11 +11,11 @@ import { name as appName } from './app.json';
import { store } from './app/store/index.js'; import { store } from './app/store/index.js';
const connectedApp = () => { const connectedApp = () => {
return ( return (
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider> </Provider>
); );
}; };
AppRegistry.registerComponent(appName, () => connectedApp); AppRegistry.registerComponent(appName, () => connectedApp);

View File

@@ -11,11 +11,11 @@ import { name as appName } from './app.json';
import { store } from './app/store/index.js'; import { store } from './app/store/index.js';
const connectedApp = () => { const connectedApp = () => {
return ( return (
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider> </Provider>
); );
}; };
AppRegistry.registerComponent(appName, () => connectedApp); AppRegistry.registerComponent(appName, () => connectedApp);

Some files were not shown because too many files have changed in this diff Show More