From 746d4ddc9d8748f7e7eafaa451166edc3be95027 Mon Sep 17 00:00:00 2001 From: Aliaksandr Paulau Date: Sat, 19 May 2018 23:40:26 +0300 Subject: [PATCH 1/3] implemented events functionality --- admin/src/App.js | 5 ++ admin/src/components/events/events-list.js | 28 +++++++ admin/src/config.js | 6 +- admin/src/ducks/events.js | 94 ++++++++++++++++++++++ admin/src/redux/reducer.js | 4 +- admin/src/redux/saga.js | 3 +- admin/src/routes/admin.js | 2 + admin/src/routes/events-page.js | 35 ++++++++ 8 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 admin/src/components/events/events-list.js create mode 100644 admin/src/ducks/events.js create mode 100644 admin/src/routes/events-page.js diff --git a/admin/src/App.js b/admin/src/App.js index 89f2dc0..8d030c8 100644 --- a/admin/src/App.js +++ b/admin/src/App.js @@ -18,6 +18,11 @@ class App extends Component { auth +
+ + events + +
diff --git a/admin/src/components/events/events-list.js b/admin/src/components/events/events-list.js new file mode 100644 index 0000000..d4bdedd --- /dev/null +++ b/admin/src/components/events/events-list.js @@ -0,0 +1,28 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { eventsSelector } from '../../ducks/events' + +class EventsList extends Component { + static propTypes = {} + + render() { + const { events } = this.props + return ( +
    + {Object.keys(events).map((key) => ( +
  1. + {events[key].title} +
    + Where: {events[key].where} +
    + When: {events[key].when} +
  2. + ))} +
+ ) + } +} + +export default connect((state) => ({ + events: eventsSelector(state) +}))(EventsList) diff --git a/admin/src/config.js b/admin/src/config.js index aa22b3b..9604855 100644 --- a/admin/src/config.js +++ b/admin/src/config.js @@ -2,15 +2,15 @@ import firebase from 'firebase/app' import 'firebase/auth' import 'firebase/database' -export const appName = 'advreact-10-05' +export const appName = 'advreact-js' export const config = { - apiKey: 'AIzaSyCbMQM0eQUSQ0SuLVAu9ZNPUcm4rdbiB8U', + apiKey: 'AIzaSyBCNW1bUz_eZtgesn-XnyzowL6Iy-6YOsQ', authDomain: `${appName}.firebaseapp.com`, databaseURL: `https://${appName}.firebaseio.com`, projectId: appName, storageBucket: '', - messagingSenderId: '1094825197832' + messagingSenderId: '203115896556' } firebase.initializeApp(config) diff --git a/admin/src/ducks/events.js b/admin/src/ducks/events.js new file mode 100644 index 0000000..5a1138f --- /dev/null +++ b/admin/src/ducks/events.js @@ -0,0 +1,94 @@ +import { appName } from '../config' +import { Record, Map } from 'immutable' +import { createSelector } from 'reselect' +import { takeEvery, all, put, call } from 'redux-saga/effects' +import firebase from 'firebase/app' + +/** + * Constants + * */ +export const moduleName = 'events' +const prefix = `${appName}/${moduleName}` +export const FETCH_EVENTS_REQUEST = `${prefix}/FETCH_EVENTS_REQUEST` +export const FETCH_EVENTS_SUCCESS = `${prefix}/FETCH_EVENTS_SUCCESS` +export const FETCH_EVENTS_FAIL = `${prefix}/FETCH_EVENTS_FAIL` + +/** + * Reducer + * */ +const ReducerState = Record({ + loading: false, + loaded: false, + entities: new Map({}) +}) + +const EventRecord = Record({ + title: null, + url: null, + where: null, + when: null, + month: null, + submissionDeadline: null +}) + +export default function reducer(state = new ReducerState(), action) { + const { type, payload } = action + + switch (type) { + case FETCH_EVENTS_REQUEST: + return state.set('loading', true) + + case FETCH_EVENTS_SUCCESS: + return state + .set('loading', false) + .set('loaded', true) + .update('entities', (entities) => + Object.entries(payload).reduce( + (map, [key, value]) => map.set(key, new EventRecord(value)), + new Map({}) + ) + ) + + case FETCH_EVENTS_FAIL: + return state.set('loading', false).set('loaded', false) + + default: + return state + } +} +/** + * Selectors + * */ + +export const stateSelector = (state) => state[moduleName] +export const eventsSelector = createSelector(stateSelector, (state) => + state.entities.toObject() +) + +/** + * Action Creators + * */ + +export function fetchEvents() { + return { type: FETCH_EVENTS_REQUEST } +} + +/** + * Sagas + * */ + +export function* fetchEventsSaga() { + const database = firebase.database().ref('/events') + + try { + const data = yield call([database, database.once], 'value') + + yield put({ type: FETCH_EVENTS_SUCCESS, payload: data.val() }) + } catch (error) { + yield put({ type: FETCH_EVENTS_FAIL, payload: error }) + } +} + +export function* saga() { + yield all([takeEvery(FETCH_EVENTS_REQUEST, fetchEventsSaga)]) +} diff --git a/admin/src/redux/reducer.js b/admin/src/redux/reducer.js index f9c1879..7e88eca 100644 --- a/admin/src/redux/reducer.js +++ b/admin/src/redux/reducer.js @@ -3,10 +3,12 @@ import { routerReducer as router } from 'react-router-redux' import { reducer as form } from 'redux-form' import authReducer, { moduleName as authModule } from '../ducks/auth' import peopleReducer, { moduleName as peopleModule } from '../ducks/people' +import eventsReducer, { moduleName as eventsModule } from '../ducks/events' export default combineReducers({ router, form, [authModule]: authReducer, - [peopleModule]: peopleReducer + [peopleModule]: peopleReducer, + [eventsModule]: eventsReducer }) diff --git a/admin/src/redux/saga.js b/admin/src/redux/saga.js index 8f1e0cd..9d0ed64 100644 --- a/admin/src/redux/saga.js +++ b/admin/src/redux/saga.js @@ -1,7 +1,8 @@ import { all } from 'redux-saga/effects' import { saga as authSaga } from '../ducks/auth' import { saga as peopleSaga } from '../ducks/people' +import { saga as eventsSaga } from '../ducks/events' export default function*() { - yield all([authSaga(), peopleSaga()]) + yield all([authSaga(), peopleSaga(), eventsSaga()]) } diff --git a/admin/src/routes/admin.js b/admin/src/routes/admin.js index aa30167..f0edbd1 100644 --- a/admin/src/routes/admin.js +++ b/admin/src/routes/admin.js @@ -1,6 +1,7 @@ import React, { Component } from 'react' import { Route } from 'react-router-dom' import PersonPage from './person-page' +import EventsPage from './events-page' class AdminPage extends Component { static propTypes = {} @@ -10,6 +11,7 @@ class AdminPage extends Component {

Admin Page

+
) } diff --git a/admin/src/routes/events-page.js b/admin/src/routes/events-page.js new file mode 100644 index 0000000..c898a97 --- /dev/null +++ b/admin/src/routes/events-page.js @@ -0,0 +1,35 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import EventsList from '../components/events/events-list' +import { fetchEvents, stateSelector } from '../ducks/events' + +class EventsPage extends Component { + static propTypes = {} + + componentWillMount = () => { + if (this.props.loading || this.props.loaded) return + + this.props.fetchEvents() + } + + render() { + const { loading, loaded } = this.props + + const body = loading && !loaded ? Loading... : + + return ( +
+

Events

+ {body} +
+ ) + } +} + +export default connect( + (state) => { + const { loading, loaded } = stateSelector(state) + return { loading, loaded } + }, + { fetchEvents } +)(EventsPage) From 788d692c8fd9e6498fd6bf032dc0713b940090a8 Mon Sep 17 00:00:00 2001 From: Aliaksandr Paulau Date: Sun, 20 May 2018 00:18:59 +0300 Subject: [PATCH 2/3] basic auth tests --- admin/src/ducks/auth.js | 10 ------ admin/src/ducks/auth.test.js | 64 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 admin/src/ducks/auth.test.js diff --git a/admin/src/ducks/auth.js b/admin/src/ducks/auth.js index 55ae8d0..ad390b5 100644 --- a/admin/src/ducks/auth.js +++ b/admin/src/ducks/auth.js @@ -61,16 +61,6 @@ export function signIn(email, password) { payload: { email, password } } } -/* -export function signIn(email, password) { - return (dispatch) => { - firebase - .auth() - .signInWithEmailAndPassword(email, password) - .then((user) => dispatch({ type: SIGN_IN_SUCCESS, payload: { user } })) - } -} -*/ /** * Sagas diff --git a/admin/src/ducks/auth.test.js b/admin/src/ducks/auth.test.js new file mode 100644 index 0000000..0211fa1 --- /dev/null +++ b/admin/src/ducks/auth.test.js @@ -0,0 +1,64 @@ +import { + signUpSaga, + SIGN_UP_REQUEST, + SIGN_UP_SUCCESS, + signInSaga, + SIGN_IN_REQUEST, + SIGN_IN_SUCCESS +} from './auth' +import { call, put, take, apply } from 'redux-saga/effects' +import firebase from 'firebase/app' + +describe('auth duck', () => { + it('should sign up', () => { + const user = { + email: 'test@test.com', + password: 'something_secret' + } + + const saga = signUpSaga({ + type: SIGN_UP_REQUEST, + payload: user + }) + + const auth = firebase.auth() + + expect(saga.next().value).toEqual( + call( + [auth, auth.createUserWithEmailAndPassword], + user.email, + user.password + ) + ) + + expect(saga.next(user).value).toEqual( + put({ type: SIGN_UP_SUCCESS, payload: { user } }) + ) + }) + + it('should sign in', () => { + const user = { + email: 'test@test.com', + password: 'something_secret' + } + + const saga = signInSaga() + + expect(saga.next().value).toEqual(take(SIGN_IN_REQUEST)) + + const auth = firebase.auth() + + expect( + saga.next({ + type: SIGN_IN_REQUEST, + payload: user + }).value + ).toEqual( + apply(auth, auth.signInWithEmailAndPassword, [user.email, user.password]) + ) + + expect(saga.next(user).value).toEqual( + put({ type: SIGN_IN_SUCCESS, payload: { user } }) + ) + }) +}) From 5bac26c69a989a1996e6fe4efaa6ceb69620cfb2 Mon Sep 17 00:00:00 2001 From: Aliaksandr Paulau Date: Sun, 20 May 2018 00:31:18 +0300 Subject: [PATCH 3/3] added negative test cases --- admin/src/ducks/auth.test.js | 64 +++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/admin/src/ducks/auth.test.js b/admin/src/ducks/auth.test.js index 0211fa1..53dd2a5 100644 --- a/admin/src/ducks/auth.test.js +++ b/admin/src/ducks/auth.test.js @@ -2,9 +2,12 @@ import { signUpSaga, SIGN_UP_REQUEST, SIGN_UP_SUCCESS, + SIGN_UP_ERROR, signInSaga, SIGN_IN_REQUEST, - SIGN_IN_SUCCESS + SIGN_IN_REQUESTS_LIMIT, + SIGN_IN_SUCCESS, + SIGN_IN_ERROR } from './auth' import { call, put, take, apply } from 'redux-saga/effects' import firebase from 'firebase/app' @@ -36,6 +39,32 @@ describe('auth duck', () => { ) }) + it('should handle failed sign up', () => { + const user = { + email: '', + password: '' + } + + const saga = signUpSaga({ + type: SIGN_UP_REQUEST, + payload: user + }) + + const auth = firebase.auth() + + expect(saga.next().value).toEqual( + call( + [auth, auth.createUserWithEmailAndPassword], + user.email, + user.password + ) + ) + + expect(saga.throw('error').value).toEqual( + put({ type: SIGN_UP_ERROR, error: 'error' }) + ) + }) + it('should sign in', () => { const user = { email: 'test@test.com', @@ -61,4 +90,37 @@ describe('auth duck', () => { put({ type: SIGN_IN_SUCCESS, payload: { user } }) ) }) + + it('should handle failed sign in', () => { + const user = { + email: '', + password: '' + } + + const saga = signInSaga() + + for (let i = 0; i < 3; i++) { + expect(saga.next().value).toEqual(take(SIGN_IN_REQUEST)) + + const auth = firebase.auth() + + expect( + saga.next({ + type: SIGN_IN_REQUEST, + payload: user + }).value + ).toEqual( + apply(auth, auth.signInWithEmailAndPassword, [ + user.email, + user.password + ]) + ) + + expect(saga.throw('error').value).toEqual( + put({ type: SIGN_IN_ERROR, error: 'error' }) + ) + } + + expect(saga.next().value).toEqual(put({ type: SIGN_IN_REQUESTS_LIMIT })) + }) })