diff --git a/admin-panel/package.json b/admin-panel/package.json
index d61a23f..db74ee9 100644
--- a/admin-panel/package.json
+++ b/admin-panel/package.json
@@ -8,6 +8,7 @@
"history": "^4.7.2",
"immutable": "^3.8.2",
"logger": "^0.0.1",
+ "prop-types": "^15.6.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-redux": "^5.0.6",
diff --git a/admin-panel/src/components/App.js b/admin-panel/src/components/App.js
index 207fb54..2c4a3b9 100644
--- a/admin-panel/src/components/App.js
+++ b/admin-panel/src/components/App.js
@@ -18,6 +18,7 @@ class App extends Component {
diff --git a/admin-panel/src/components/events/EventList.js b/admin-panel/src/components/events/EventList.js
new file mode 100644
index 0000000..eac0d12
--- /dev/null
+++ b/admin-panel/src/components/events/EventList.js
@@ -0,0 +1,72 @@
+import React, {Component} from 'react'
+import firebase from 'firebase/index'
+
+class EventList extends Component {
+ static propTypes = {
+
+ };
+
+ state = {
+ events: null
+ }
+
+ componentDidMount = () => {
+ const eventsRef = firebase.database().ref('events/');
+
+ eventsRef.on('value', (snapshot) => {
+ let events = []
+
+ snapshot.forEach((childSnapshot) => {
+ events.push({
+ id: childSnapshot.key,
+ ...childSnapshot.val()
+ })
+ });
+
+ this.setState({events: events});
+ });
+ }
+
+ getEventList = (events) => {
+ return (
+
+
+
+ | Month |
+ Deadline |
+ Title |
+ URL |
+ When |
+ Where |
+
+
+
+ {
+ events.map(event =>
+
+ | {event.month} |
+ {event.submissionDeadline} |
+ {event.title} |
+ {event.url} |
+ {event.when} |
+ {event.where} |
+
+ )
+ }
+
+
+ )
+ }
+
+ render() {
+ const {events} = this.state
+
+ return (
+
+ {!events ?
List is empty
: this.getEventList(events)}
+
+ )
+ }
+}
+
+export default EventList
\ No newline at end of file
diff --git a/admin-panel/src/components/events/EventsInfiniteLoaderVirtualized.js b/admin-panel/src/components/events/EventsInfiniteLoaderVirtualized.js
new file mode 100644
index 0000000..6dce520
--- /dev/null
+++ b/admin-panel/src/components/events/EventsInfiniteLoaderVirtualized.js
@@ -0,0 +1,78 @@
+import React, {Component} from 'react'
+import {connect} from 'react-redux'
+import {
+ fetchLimitEvents,
+ selectEvent,
+ fetchAllEvents,
+ eventLimitListSelector,
+ eventListSelector
+} from '../../ducks/events'
+import {InfiniteLoader, List} from 'react-virtualized'
+import 'react-virtualized/styles.css'
+
+export class EventsInfiniteLoaderVirtualized extends Component {
+ static propTypes = {};
+
+ componentDidMount() {
+ this.props.fetchAllEvents()
+ this.loadMoreRows({startIndex: 0, stopIndex: 10})
+ }
+
+ loadMoreRows = ({startIndex, stopIndex}) => {
+ this.props.fetchLimitEvents(startIndex, stopIndex)
+ }
+
+ isRowLoaded = ({index}) => {
+ return !!this.props.events[index];
+ }
+
+ rowRenderer = ({key, index, style}) => {
+ const {events} = this.props
+
+ return (
+
+ {events[index] && this.getRow(events[index])}
+
+ )
+ }
+
+ getRow = (event) => (
+
+ {event.title}
+ {event.when}
+ {event.where}
+
+ )
+
+ render() {
+ const {eventsTotal, loading} = this.props
+
+ return (
+
+ {({onRowsRendered, registerChild}) => (
+
+ )}
+
+ )
+ }
+}
+
+export default connect((state) => ({
+ events: eventLimitListSelector(state),
+ eventsTotal: eventListSelector(state).length,
+}), {fetchLimitEvents, selectEvent, fetchAllEvents})(EventsInfiniteLoaderVirtualized)
\ No newline at end of file
diff --git a/admin-panel/src/components/events/EventsTableVirtualized.js b/admin-panel/src/components/events/EventsTableVirtualized.js
index c4f8c3e..662fa6d 100644
--- a/admin-panel/src/components/events/EventsTableVirtualized.js
+++ b/admin-panel/src/components/events/EventsTableVirtualized.js
@@ -16,6 +16,7 @@ export class EventsTableVirtualized extends Component {
render() {
if (this.props.loading) return
+
return (
-
diff --git a/admin-panel/src/components/people/PeopleList.js b/admin-panel/src/components/people/PeopleList.js
index 1fe2549..59549a4 100644
--- a/admin-panel/src/components/people/PeopleList.js
+++ b/admin-panel/src/components/people/PeopleList.js
@@ -1,7 +1,8 @@
import React, { Component } from 'react'
import {connect} from 'react-redux'
-import {peopleListSelector} from '../../ducks/people'
+import {peopleListSelector, fetchAllPeople, loadingSelector} from '../../ducks/people'
import {List} from 'react-virtualized'
+import Loader from '../common/Loader'
import 'react-virtualized/styles.css'
class PeopleList extends Component {
@@ -9,10 +10,18 @@ class PeopleList extends Component {
};
+ componentDidMount() {
+ this.props.fetchAllPeople()
+ }
+
render() {
+ const {people, loading} = this.props
+
+ if (loading) return
+
return ({
- people: peopleListSelector(state)
-}))(PeopleList)
\ No newline at end of file
+ people: peopleListSelector(state),
+ loading: loadingSelector(state),
+}), {fetchAllPeople})(PeopleList)
\ No newline at end of file
diff --git a/admin-panel/src/components/routes/EventsPage.js b/admin-panel/src/components/routes/EventsPage.js
index eedf79d..9232c29 100644
--- a/admin-panel/src/components/routes/EventsPage.js
+++ b/admin-panel/src/components/routes/EventsPage.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import EventsTableVirtualized from '../events/EventsTableVirtualized'
+import EventsInfiniteLoaderVirtualized from '../events/EventsInfiniteLoaderVirtualized'
class EventsPage extends Component {
static propTypes = {
@@ -9,7 +10,7 @@ class EventsPage extends Component {
render() {
return (
-
+
)
}
diff --git a/admin-panel/src/components/routes/auth/index.js b/admin-panel/src/components/routes/auth/index.js
index 91c9bab..f75ee3c 100644
--- a/admin-panel/src/components/routes/auth/index.js
+++ b/admin-panel/src/components/routes/auth/index.js
@@ -1,16 +1,22 @@
import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import {Route, NavLink} from 'react-router-dom'
import {connect} from 'react-redux'
+
import {signIn, signUp} from '../../../ducks/auth'
+import {errorSelector, loadingSelector} from '../../../ducks/auth'
import SignInForm from '../../auth/SignInForm'
import SignUpForm from '../../auth/SignUpForm'
class Auth extends Component {
static propTypes = {
-
+ authError: PropTypes.object,
+ authLoading: PropTypes.bool.isRequired
};
render() {
+ const {authLoading, authError} = this.props;
+
return (
Auth page
@@ -18,7 +24,7 @@ class Auth extends Component {
Sign In
Sign Up
- } />
+ } />
} />
)
@@ -29,4 +35,7 @@ class Auth extends Component {
}
-export default connect(null, { signIn, signUp })(Auth)
\ No newline at end of file
+export default connect(state => ({
+ authError: errorSelector(state),
+ authLoading: loadingSelector(state)
+}), { signIn, signUp })(Auth)
\ No newline at end of file
diff --git a/admin-panel/src/components/user/NewUserForm.js b/admin-panel/src/components/user/NewUserForm.js
new file mode 100644
index 0000000..605022d
--- /dev/null
+++ b/admin-panel/src/components/user/NewUserForm.js
@@ -0,0 +1,50 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import {reduxForm, Field} from 'redux-form';
+import validator from 'email-validator';
+
+import ErrorField from '../common/ErrorField';
+
+class NewUserForm extends Component {
+ static propTypes = {
+ handleSubmit: PropTypes.func.isRequired
+ };
+
+ render() {
+ return (
+
+ )
+ }
+}
+
+const validate = ({ firstName, lastName, email }) => {
+ const errors = {};
+
+ if (!email) errors.email = 'Email is a required field';
+ if (email && !validator.validate(email)) errors.email = 'Incorrect email format';
+
+ if (!firstName) errors.firstName = 'First name is a required field';
+ if (!lastName) errors.lastName = 'Last name is a required field';
+
+ return errors;
+}
+
+export default reduxForm({
+ form: 'new-user',
+ validate
+})(NewUserForm)
\ No newline at end of file
diff --git a/admin-panel/src/config.js b/admin-panel/src/config.js
index 65bba00..202b263 100644
--- a/admin-panel/src/config.js
+++ b/admin-panel/src/config.js
@@ -1,14 +1,14 @@
-import firebase from 'firebase'
+import firebase from 'firebase';
-export const appName = 'advreact-04-12'
+export const appName = 'adv-react-8c4fb';
const config = {
- apiKey: "AIzaSyCmDWlgYIhtEr1pWjgKYds3iXKWBl9wbjE",
+ apiKey: "AIzaSyCPlodrXJgpCXqDWklsFc_V5vHGiD6G2Uc",
authDomain: `${appName}.firebaseapp.com`,
databaseURL: `https://${appName}.firebaseio.com`,
- projectId: appName,
- storageBucket: "",
- messagingSenderId: "95255462276"
-}
+ projectId: {appName},
+ storageBucket: "adv-react-8c4fb.appspot.com",
+ messagingSenderId: "47356959167"
+};
-firebase.initializeApp(config)
\ No newline at end of file
+firebase.initializeApp(config);
\ No newline at end of file
diff --git a/admin-panel/src/ducks/events.js b/admin-panel/src/ducks/events.js
index f13e342..93a344e 100644
--- a/admin-panel/src/ducks/events.js
+++ b/admin-panel/src/ducks/events.js
@@ -1,6 +1,6 @@
import {all, takeEvery, put, call} from 'redux-saga/effects'
import {appName} from '../config'
-import {Record, OrderedSet, OrderedMap} from 'immutable'
+import {Record, OrderedSet, OrderedMap, List} from 'immutable'
import firebase from 'firebase'
import {createSelector} from 'reselect'
import {fbToEntities} from './utils'
@@ -14,6 +14,9 @@ const prefix = `${appName}/${moduleName}`
export const FETCH_ALL_REQUEST = `${prefix}/FETCH_ALL_REQUEST`
export const FETCH_ALL_START = `${prefix}/FETCH_ALL_START`
export const FETCH_ALL_SUCCESS = `${prefix}/FETCH_ALL_SUCCESS`
+export const FETCH_LIMIT_REQUEST = `${prefix}/FETCH_LIMIT_REQUEST`
+export const FETCH_LIMIT_START = `${prefix}/FETCH_LIMIT_START`
+export const FETCH_LIMIT_SUCCESS = `${prefix}/FETCH_LIMIT_SUCCESS`
export const SELECT = `${prefix}/SELECT`
@@ -22,9 +25,12 @@ export const SELECT = `${prefix}/SELECT`
* */
export const ReducerRecord = Record({
loading: false,
+ loadingLimit: false,
loaded: false,
+ loadedLimit: false,
selected: new OrderedSet(),
- entities: new OrderedMap({})
+ entities: new OrderedMap({}),
+ entitiesLimit: new List([])
})
export const EventRecord = Record({
@@ -43,13 +49,18 @@ export default function reducer(state = new ReducerRecord(), action) {
switch (type) {
case FETCH_ALL_START:
return state.set('loading', true)
-
+ case FETCH_LIMIT_START:
+ return state.set('loadingLimit', true)
case FETCH_ALL_SUCCESS:
return state
.set('loading', false)
.set('loaded', true)
.set('entities', fbToEntities(payload, EventRecord))
-
+ case FETCH_LIMIT_SUCCESS:
+ return state
+ .set('loadingLimit', false)
+ .set('loadedLimit', true)
+ .set('entitiesLimit', fbToEntities(payload, EventRecord))
case SELECT:
return state.update('selected', selected => selected.has(payload.uid)
? selected.remove(payload.uid)
@@ -67,9 +78,13 @@ export default function reducer(state = new ReducerRecord(), action) {
export const stateSelector = state => state[moduleName]
export const entitiesSelector = createSelector(stateSelector, state => state.entities)
+export const entitiesLimitSelector = createSelector(stateSelector, state => state.entitiesLimit)
export const loadingSelector = createSelector(stateSelector, state => state.loading)
+export const loadingLimitSelector = createSelector(stateSelector, state => state.loadingLimit)
export const loadedSelector = createSelector(stateSelector, state => state.loaded)
+export const loadedLimitSelector = createSelector(stateSelector, state => state.loadedLimit)
export const eventListSelector = createSelector(entitiesSelector, entities => entities.valueSeq().toArray())
+export const eventLimitListSelector = createSelector(entitiesLimitSelector, entitiesLimitSelector => entitiesLimitSelector.valueSeq().toArray())
/**
* Action Creators
@@ -81,6 +96,13 @@ export function fetchAllEvents() {
}
}
+export function fetchLimitEvents(startAt, limit) {
+ return {
+ type: FETCH_LIMIT_REQUEST,
+ payload: {startAt, limit}
+ }
+}
+
export function selectEvent(uid) {
return {
type: SELECT,
@@ -101,16 +123,32 @@ export function* fetchAllSaga() {
const snapshot = yield call([ref, ref.once], 'value')
- console.log('---', snapshot)
-
yield put({
type: FETCH_ALL_SUCCESS,
payload: snapshot.val()
})
}
+export function* fetchLimitSaga(action) {
+
+ const {startAt, limit} = action.payload
+ const ref = firebase.database().ref('events').orderByKey().startAt(startAt.toString()).limitToFirst(limit)
+
+ yield put({
+ type: FETCH_LIMIT_START
+ })
+
+ const snapshot = yield call([ref, ref.once], 'value')
+
+ yield put({
+ type: FETCH_LIMIT_SUCCESS,
+ payload: snapshot.val()
+ })
+}
+
export function* saga() {
yield all([
- takeEvery(FETCH_ALL_REQUEST, fetchAllSaga)
+ takeEvery(FETCH_ALL_REQUEST, fetchAllSaga),
+ takeEvery(FETCH_LIMIT_REQUEST, fetchLimitSaga)
])
}
\ No newline at end of file
diff --git a/admin-panel/src/ducks/people.js b/admin-panel/src/ducks/people.js
index 9a5f996..cf15eed 100644
--- a/admin-panel/src/ducks/people.js
+++ b/admin-panel/src/ducks/people.js
@@ -3,20 +3,31 @@ import {Record, OrderedMap} from 'immutable'
import {createSelector} from 'reselect'
import {put, call, all, takeEvery} from 'redux-saga/effects'
import {reset} from 'redux-form'
-import {generateId} from './utils'
+import firebase from "firebase/index"
+import {fbToEntities} from "./utils"
/**
* Constants
* */
export const moduleName = 'people'
const prefix = `${appName}/${moduleName}`
-export const ADD_PERSON = `${prefix}/ADD_PERSON`
+export const ADD_PERSON_REQUEST = `${prefix}/ADD_PERSON_REQUEST`
+export const ADD_PERSON_START = `${prefix}/ADD_PERSON_START`
export const ADD_PERSON_SUCCESS = `${prefix}/ADD_PERSON_SUCCESS`
+export const ADD_PERSON_ERROR = `${prefix}/ADD_PERSON_ERROR`
+export const FETCH_ALL_REQUEST = `${prefix}/FETCH_ALL_REQUEST`
+export const FETCH_ALL_START = `${prefix}/FETCH_ALL_START`
+export const FETCH_ALL_SUCCESS = `${prefix}/FETCH_ALL_SUCCESS`
/**
* Reducer
* */
const ReducerState = Record({
+ addPersonLoading: false,
+ addPersonSuccess: false,
+ addPersonError: null,
+ loading: false,
+ loaded: false,
entities: new OrderedMap({})
})
@@ -31,9 +42,24 @@ export default function reducer(state = new ReducerState(), action) {
const {type, payload} = action
switch (type) {
+ case ADD_PERSON_START:
+ return state.set('addPersonLoading', true)
case ADD_PERSON_SUCCESS:
- return state.setIn(['entities', payload.uid],new PersonRecord(payload))
+ return state
+ .set('addPersonLoading', false)
+ .set('addPersonSuccess', true)
+ case ADD_PERSON_ERROR:
+ return state
+ .set('addPersonLoading', false)
+ .set('addPersonError', payload.error.message)
+ case FETCH_ALL_START:
+ return state.set('loading', true)
+ case FETCH_ALL_SUCCESS:
+ return state
+ .set('loading', false)
+ .set('loaded', true)
+ .set('entities', fbToEntities(payload, PersonRecord))
default:
return state
}
@@ -43,6 +69,11 @@ export default function reducer(state = new ReducerState(), action) {
* Selectors
* */
export const stateSelector = state => state[moduleName]
+export const addPersonLoadingSelector = createSelector(stateSelector, state => state.addPersonLoading)
+export const addPersonSuccessSelector = createSelector(stateSelector, state => state.addPersonSuccess)
+export const addPersonErrorSelector = createSelector(stateSelector, state => state.addPersonError)
+export const loadingSelector = createSelector(stateSelector, state => state.loading)
+export const loadedSelector = createSelector(stateSelector, state => state.loaded)
export const entitiesSelector = createSelector(stateSelector, state => state.entities)
export const peopleListSelector = createSelector(entitiesSelector, entities => entities.valueSeq().toArray())
@@ -52,11 +83,17 @@ export const peopleListSelector = createSelector(entitiesSelector, entities => e
export function addPerson(person) {
return {
- type: ADD_PERSON,
+ type: ADD_PERSON_REQUEST,
payload: { person }
}
}
+export function fetchAllPeople() {
+ return {
+ type: FETCH_ALL_REQUEST
+ }
+}
+
/**
* Sagas
*/
@@ -64,18 +101,52 @@ export function addPerson(person) {
export const addPersonSaga = function * (action) {
const { person } = action.payload
- const uid = yield call(generateId)
+ const ref = firebase.database().ref('/people')
yield put({
- type: ADD_PERSON_SUCCESS,
- payload: {uid, ...person}
+ type: ADD_PERSON_START
})
- yield put(reset('person'))
+ try {
+ yield call([ref, ref.push], person)
+
+ yield put({
+ type: ADD_PERSON_SUCCESS,
+ })
+
+ // update peolple
+ yield put({
+ type: FETCH_ALL_REQUEST
+ })
+
+ yield put(reset('person'))
+ } catch (error) {
+
+ yield put({
+ type: ADD_PERSON_ERROR,
+ payload: {error}
+ })
+ }
+}
+
+export function* fetchAllSaga() {
+ const ref = firebase.database().ref('people')
+
+ yield put({
+ type: FETCH_ALL_START
+ })
+
+ const snapshot = yield call([ref, ref.once], 'value')
+
+ yield put({
+ type: FETCH_ALL_SUCCESS,
+ payload: snapshot.val()
+ })
}
export const saga = function * () {
yield all([
- takeEvery(ADD_PERSON, addPersonSaga)
+ takeEvery(ADD_PERSON_REQUEST, addPersonSaga),
+ takeEvery(FETCH_ALL_REQUEST, fetchAllSaga)
])
}
\ No newline at end of file
diff --git a/admin-panel/src/ducks/utils.js b/admin-panel/src/ducks/utils.js
index df63cc2..d459ddb 100644
--- a/admin-panel/src/ducks/utils.js
+++ b/admin-panel/src/ducks/utils.js
@@ -1,4 +1,4 @@
-import {OrderedMap} from 'immutable'
+import {OrderedMap, List} from 'immutable'
export function generateId() {
return Date.now()
@@ -10,4 +10,4 @@ export function fbToEntities(values, DataRecord) {
(acc, [uid, value]) => acc.set(uid, new DataRecord({ uid, ...value })),
new OrderedMap({})
)
-}
\ No newline at end of file
+}