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 (
+
+
+
+ {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 ? (
+
+ ) : (
+ Loading conferences list
+ )
+ }
+}
+
+export default connect(
+ (state) => ({
+ events: eventsSelector(state),
+ isLoading: isLoadingSelector(state)
+ }),
+ { fetchEvents }
+)(EventsList)
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)
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)
+ })
+})
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