From 3b59834dc4170ed5612f8b4f05dec8fcd50d9782 Mon Sep 17 00:00:00 2001 From: Personal Date: Sat, 7 Jul 2018 08:28:10 +0200 Subject: [PATCH 1/3] link firebase to my own firebase instance --- admin/src/config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/src/config.js b/admin/src/config.js index a6889dc..ad91bca 100644 --- a/admin/src/config.js +++ b/admin/src/config.js @@ -2,15 +2,15 @@ import { initializeApp } from 'firebase/app' import 'firebase/auth' import 'firebase/database' -export const appName = 'adv-react-25-06' +export const appName = 'advreact-25-06-c6bae' const config = { - apiKey: 'AIzaSyDzqwnZ_39QyqhxYZVPjVH8eBww7DUBmVc', + apiKey: 'AIzaSyBM5eZETRfWWS9BoQ-8guawlYzeW7dDToE', authDomain: `${appName}.firebaseapp.com`, databaseURL: `https://${appName}.firebaseio.com`, projectId: appName, storageBucket: '', - messagingSenderId: '874599443389' + messagingSenderId: '985221034928' } initializeApp(config) From 51bad71c2d8ae4492aeb3a0f0c9b3398b306e52b Mon Sep 17 00:00:00 2001 From: Personal Date: Sat, 7 Jul 2018 08:28:26 +0200 Subject: [PATCH 2/3] cover auth duck with tests --- admin/src/ducks/auth.spec.js | 127 +++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 admin/src/ducks/auth.spec.js diff --git a/admin/src/ducks/auth.spec.js b/admin/src/ducks/auth.spec.js new file mode 100644 index 0000000..4f8cf65 --- /dev/null +++ b/admin/src/ducks/auth.spec.js @@ -0,0 +1,127 @@ +import firebase from 'firebase/app' +import { apply, call, take, put } from 'redux-saga/effects' +import reducer, { + ReducerRecord, + SIGN_IN_REQUEST, + SIGN_IN_SUCCESS, + SIGN_IN_MAX_TRIES_ERROR, + SIGN_UP_REQUEST, + SIGN_UP_SUCCESS, + signInSaga, + signUpSaga, + signIn, + signUp +} from './auth' + +describe('Saga', () => { + const payload = { + email: 'test@test.com', + password: 'random123' + } + + it('should sign in', () => { + const action = { + type: SIGN_IN_REQUEST, + payload + } + + const saga = signInSaga(action) + const auth = firebase.auth() + + for (let i = 0; i < 3; i++) { + expect(saga.next().value).toEqual(take(SIGN_IN_REQUEST)) + + expect(saga.next({ payload }).value).toEqual( + apply(auth, auth.signInWithEmailAndPassword, [ + payload.email, + payload.password + ]) + ) + } + + expect(saga.next().value).toEqual(put({ type: SIGN_IN_MAX_TRIES_ERROR })) + }) + + it('should sign up', () => { + const user = { + email: 'test@test.com', + password: 'random123' + } + + const saga = signUpSaga({ payload }) + const auth = firebase.auth() + + expect(saga.next().value).toEqual( + call( + [auth, auth.createUserWithEmailAndPassword], + payload.email, + payload.password + ) + ) + + expect(saga.next(user).value).toEqual( + put({ + type: SIGN_UP_SUCCESS, + payload: { user } + }) + ) + }) +}) + +describe('Action', () => { + it('should return SIGN_IN_REQUEST action', () => { + const email = 'test@test.com' + const password = 'random123' + + expect(signIn(email, password)).toEqual({ + type: SIGN_IN_REQUEST, + payload: { email, password } + }) + }) + + it('should return SIGN_UP_REQUEST action', () => { + const email = 'test@test.com' + const password = 'random123' + + expect(signUp(email, password)).toEqual({ + type: SIGN_UP_REQUEST, + payload: { email, password } + }) + }) + + it('should return SIGN_UP_REQUEST action', () => { + const email = 'test@test.com' + const password = 'random123' + + expect(signUp(email, password)).toEqual({ + type: SIGN_UP_REQUEST, + payload: { email, password } + }) + }) +}) + +describe('Reducer', () => { + it('should return the updated state with signed user', () => { + const user = { + email: 'test@test.com', + password: 'random123' + } + + const action = { + type: SIGN_IN_SUCCESS, + payload: { user } + } + + const state = new ReducerRecord() + + expect(reducer(undefined, action)).toEqual(state.set('user', { ...user })) + }) + + it('should return default state state with signed user', () => { + const action = { type: 'ANY_OTHER_ACTION' } + + const state = new ReducerRecord() + + expect(reducer(undefined, action)).toEqual(state) + }) +}) From d07085ccb1bd21abf5217c55e1a3e96ed8028a4d Mon Sep 17 00:00:00 2001 From: Personal Date: Sun, 8 Jul 2018 11:13:08 +0200 Subject: [PATCH 3/3] implement events list page --- admin/src/App.js | 2 + admin/src/components/events/events-item.js | 18 ++++ admin/src/components/events/events-list.js | 58 +++++++++++ admin/src/ducks/events.js | 114 +++++++++++++++++++++ admin/src/redux/reducer.js | 2 + admin/src/redux/saga.js | 3 +- admin/src/routes/events.js | 16 +++ 7 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 admin/src/components/events/events-item.js 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.js diff --git a/admin/src/App.js b/admin/src/App.js index c54f214..622776c 100644 --- a/admin/src/App.js +++ b/admin/src/App.js @@ -3,12 +3,14 @@ import { Route } from 'react-router-dom' import ProtectedRoute from './components/common/protected-route' import Auth from './routes/auth' import Admin from './routes/admin' +import Events from './routes/events' class App extends Component { render() { return (
+
) diff --git a/admin/src/components/events/events-item.js b/admin/src/components/events/events-item.js new file mode 100644 index 0000000..f9a6037 --- /dev/null +++ b/admin/src/components/events/events-item.js @@ -0,0 +1,18 @@ +import React, { Component } from 'react' + +export default class EventsItem extends Component { + render() { + const { title, url, where, month } = this.props + + return ( +
  • +

    + {title} +

    + + {where} in {month} + +
  • + ) + } +} diff --git a/admin/src/components/events/events-list.js b/admin/src/components/events/events-list.js new file mode 100644 index 0000000..c02a706 --- /dev/null +++ b/admin/src/components/events/events-list.js @@ -0,0 +1,58 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { + fetchEvents, + eventsSelector, + isLoadingSelector +} from '../../ducks/events' +import EventsItem from './events-item' + +class EventsList extends Component { + componentDidMount() { + this.props.fetchEvents() + } + + renderItems = () => { + return this.props.events.map((conference) => { + const { + id, + title, + url, + where, + when, + month, + submissionDeadline + } = conference + + return ( + + ) + }) + } + + render() { + const { events, isLoading } = this.props + + return events && !isLoading ? ( +
      {this.renderItems()}
    + ) : ( +
    Loading conferences list
    + ) + } +} + +export default connect( + (state) => ({ + events: eventsSelector(state), + isLoading: isLoadingSelector(state) + }), + { fetchEvents } +)(EventsList) diff --git a/admin/src/ducks/events.js b/admin/src/ducks/events.js new file mode 100644 index 0000000..2ca0c93 --- /dev/null +++ b/admin/src/ducks/events.js @@ -0,0 +1,114 @@ +import { appName } from '../config' +import { Record, OrderedMap } from 'immutable' +import firebase from 'firebase/app' +import { takeLatest, put, call } from 'redux-saga/effects' +import { createSelector } from 'reselect' + +/** + * 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_ERROR = `${prefix}/FETCH_EVENTS_ERROR` + +/** + * Reducer + * */ +const ReducerState = Record({ + data: new OrderedMap(), + isLoading: false, + error: null +}) + +const EventRecord = Record({ + id: null, + title: null, + url: null, + where: null, + when: null, + month: null, + submissionDeadline: null +}) + +export default function reducer(state = new ReducerState(), action) { + const { type, payload, error } = action + + switch (type) { + case FETCH_EVENTS_REQUEST: { + return state.set('isLoading', true) + } + + case FETCH_EVENTS_SUCCESS: { + return state + .set('isLoading', false) + .set('data', createEventsRecords(payload)) + } + + case FETCH_EVENTS_ERROR: { + return state.set('isLoading', false).set('error', error) + } + + default: + return state + } +} + +/** + * Selectors + * */ +export const stateSelector = (state) => state[moduleName] +export const eventsSelector = createSelector(stateSelector, (state) => + state.data.valueSeq().toArray() +) +export const errorSeloctor = createSelector( + stateSelector, + (state) => state.error +) +export const isLoadingSelector = createSelector( + stateSelector, + (state) => state.isLoading +) + +/** + * Action Creators + * */ +export function fetchEvents() { + return { + type: FETCH_EVENTS_REQUEST + } +} + +/** + * Sagas + */ +export function* fetchEventsSaga() { + const eventsRef = firebase.database().ref('events') + + try { + const events = yield call([eventsRef, eventsRef.once], 'value') + + yield put({ + type: FETCH_EVENTS_SUCCESS, + payload: events.val() + }) + } catch (error) { + yield put({ + type: FETCH_EVENTS_ERROR, + error + }) + } +} + +export function* saga() { + yield takeLatest(FETCH_EVENTS_REQUEST, fetchEventsSaga) +} + +function createEventsRecords(events) { + return Object.entries(events).reduce( + (eventsMap, [id, value]) => + eventsMap.set(id, new EventRecord({ id, ...value })), + new OrderedMap({}) + ) +} diff --git a/admin/src/redux/reducer.js b/admin/src/redux/reducer.js index 60ea924..2759f65 100644 --- a/admin/src/redux/reducer.js +++ b/admin/src/redux/reducer.js @@ -2,9 +2,11 @@ import { combineReducers } from '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({ form, [authModule]: authReducer, + [eventsModule]: eventsReducer, [peopleModule]: peopleReducer }) diff --git a/admin/src/redux/saga.js b/admin/src/redux/saga.js index ca88652..71a1ad8 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 peopleSaga } from '../ducks/people' import { saga as authSaga } from '../ducks/auth' +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/events.js b/admin/src/routes/events.js new file mode 100644 index 0000000..d05e632 --- /dev/null +++ b/admin/src/routes/events.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' +import { Route } from 'react-router-dom' +import EventsList from '../components/events/events-list' + +class Events extends Component { + render() { + return ( +
    +

    Events

    + +
    + ) + } +} + +export default Events