diff --git a/admin/src/App.js b/admin/src/App.js
index c54f214..0f7f786 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/common/loader.js b/admin/src/components/common/loader.js
new file mode 100644
index 0000000..a169e58
--- /dev/null
+++ b/admin/src/components/common/loader.js
@@ -0,0 +1,3 @@
+import React from 'react'
+
+export default () => loading...
diff --git a/admin/src/components/events/list.js b/admin/src/components/events/list.js
new file mode 100644
index 0000000..90e8a5e
--- /dev/null
+++ b/admin/src/components/events/list.js
@@ -0,0 +1,7 @@
+import React from 'react'
+
+const EventsList = ({ events }) => {
+ return {events.map((event, i) => - {event.title}
)}
+}
+
+export default EventsList
diff --git a/admin/src/ducks/auth.spec.js b/admin/src/ducks/auth.spec.js
new file mode 100644
index 0000000..24d5c0b
--- /dev/null
+++ b/admin/src/ducks/auth.spec.js
@@ -0,0 +1,107 @@
+import { call, take, put, apply } from 'redux-saga/effects'
+import firebase from 'firebase/app'
+import {
+ SIGN_IN_REQUEST,
+ SIGN_IN_ERROR,
+ SIGN_UP_SUCCESS,
+ SIGN_UP_ERROR,
+ SIGN_IN_MAX_TRIES_ERROR,
+ signInSaga,
+ signUpSaga
+} from './auth'
+
+describe('Saga', () => {
+ describe('sign in', () => {
+ it('should success', () => {
+ const saga = signInSaga()
+
+ expect(saga.next().value).toEqual(take(SIGN_IN_REQUEST))
+
+ const payload = { email: 'test@test.com', password: 'test password' }
+ const auth = firebase.auth()
+
+ expect(saga.next({ payload }).value).toEqual(
+ apply(auth, auth.signInWithEmailAndPassword, [
+ payload.email,
+ payload.password
+ ])
+ )
+ })
+
+ const checkIncorrectSignIn = (saga) => {
+ expect(saga.next().value).toEqual(take(SIGN_IN_REQUEST))
+
+ const payload = { email: 'test@test.com', password: 'test password' }
+ const auth = firebase.auth()
+
+ expect(saga.next({ payload }).value).toEqual(
+ apply(auth, auth.signInWithEmailAndPassword, [
+ payload.email,
+ payload.password
+ ])
+ )
+ const error = new Error('error')
+ expect(saga.throw(error).value).toEqual(
+ put({ type: SIGN_IN_ERROR, error })
+ )
+ }
+
+ it('should error', () => {
+ const saga = signInSaga()
+
+ for (let i = 0; i < 3; i++) {
+ checkIncorrectSignIn(saga)
+ }
+
+ expect(saga.next().value).toEqual(
+ put({
+ type: SIGN_IN_MAX_TRIES_ERROR
+ })
+ )
+ })
+ })
+
+ describe('sign up', () => {
+ it('should success', () => {
+ const action = {
+ payload: { email: 'test@test.conm', password: 'pass12345678' }
+ }
+ const { payload } = action
+ const auth = firebase.auth()
+
+ const saga = signUpSaga(action)
+ expect(saga.next().value).toEqual(
+ call(
+ [auth, auth.createUserWithEmailAndPassword],
+ payload.email,
+ payload.password
+ )
+ )
+ const user = { firstName: 'testName' }
+ expect(saga.next(user).value).toEqual(
+ put({ type: SIGN_UP_SUCCESS, payload: { user } })
+ )
+ })
+
+ it('should error', () => {
+ const action = {
+ payload: { email: 'test@test.conm', password: 'pass12345678' }
+ }
+ const { payload } = action
+ const auth = firebase.auth()
+
+ const saga = signUpSaga(action)
+ expect(saga.next().value).toEqual(
+ call(
+ [auth, auth.createUserWithEmailAndPassword],
+ payload.email,
+ payload.password
+ )
+ )
+ const error = new Error('error msg')
+ expect(saga.throw(error).value).toEqual(
+ put({ type: SIGN_UP_ERROR, error })
+ )
+ })
+ })
+})
diff --git a/admin/src/ducks/events.js b/admin/src/ducks/events.js
new file mode 100644
index 0000000..0c7916a
--- /dev/null
+++ b/admin/src/ducks/events.js
@@ -0,0 +1,90 @@
+import { appName } from '../config'
+import { Record, List } from 'immutable'
+import { createSelector } from 'reselect'
+import firebase from 'firebase/app'
+import { put, takeEvery, call } from 'redux-saga/effects'
+
+/**
+ * Constants
+ * */
+export const moduleName = 'events'
+const prefix = `${appName}/${moduleName}`
+export const EVENTS_REQUEST = `${prefix}/EVENTS_REQUES`
+export const EVENTS_REQUES_SUCCESS = `${prefix}/EVENTS_REQUES_SUCCESS`
+
+/**
+ * Reducer
+ * */
+const ReducerState = Record({
+ entities: new List([]),
+ loading: false
+})
+
+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 EVENTS_REQUEST:
+ return state.set('loading', true)
+ case EVENTS_REQUES_SUCCESS:
+ const { events } = payload
+ const newState = state.set('loading', false)
+ return Object.keys(events).reduce((acc, key) => {
+ return acc.update('entities', (entities) =>
+ entities.push(new EventRecord(events[key]))
+ )
+ }, newState)
+ default:
+ return state
+ }
+}
+/**
+ * Selectors
+ * */
+
+export const stateSelector = (state) => state[moduleName]
+export const eventsSelector = createSelector(stateSelector, (state) =>
+ state.entities.valueSeq().toArray()
+)
+export const loadingSelector = createSelector(
+ stateSelector,
+ (state) => state.loading
+)
+
+/**
+ * Action Creators
+ * */
+
+export function loadEvents() {
+ return {
+ type: EVENTS_REQUEST
+ }
+}
+
+/**
+ * Sagas
+ */
+
+export function* loadEvensSaga(action) {
+ const ref = firebase.database().ref('events')
+ const data = yield call([ref, ref.once], 'value')
+ const events = data.val()
+
+ yield put({
+ type: EVENTS_REQUES_SUCCESS,
+ payload: { events }
+ })
+}
+
+export function* saga() {
+ yield takeEvery(EVENTS_REQUEST, loadEvensSaga)
+}
diff --git a/admin/src/redux/reducer.js b/admin/src/redux/reducer.js
index 60ea924..c28e1e8 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,
- [peopleModule]: peopleReducer
+ [peopleModule]: peopleReducer,
+ [eventsModule]: eventsReducer
})
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..0b5d20b
--- /dev/null
+++ b/admin/src/routes/events.js
@@ -0,0 +1,31 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+
+import { loadEvents, loadingSelector, eventsSelector } from '../ducks/events'
+import Loader from '../components/common/loader'
+import EventsList from '../components/events/list'
+
+class EventsRoute extends Component {
+ componentDidMount() {
+ this.props.loadEvents()
+ }
+
+ render() {
+ const { loading, events } = this.props
+ return (
+
+
Events Page
+ {loading && }
+
+
+ )
+ }
+}
+
+export default connect(
+ (state) => ({
+ loading: loadingSelector(state),
+ events: eventsSelector(state)
+ }),
+ { loadEvents }
+)(EventsRoute)