diff --git a/admin/src/components/events/events-list.js b/admin/src/components/events/events-list.js new file mode 100644 index 0000000..00a8880 --- /dev/null +++ b/admin/src/components/events/events-list.js @@ -0,0 +1,49 @@ +import React, { Component, Fragment } from 'react' +import { getEvents, eventsSelector, moduleName } from '../../ducks/events' +import { connect } from 'react-redux' +import './events.css' + +class EventsList extends Component { + componentDidMount() { + this.props.getEvents() + } + + render() { + const { events, fetching } = this.props + { + if (fetching) { + return
Loading...
+ } else { + return ( +
+ {events.map((event) => ( +
+
{event.title}
+
{event.when}
+
{event.where}
+
{event.url}
+
+ ))} +
+ ) + } + } + return ( +
+ {fetching ? ( +
(
+ ) : ( +
{events.map((event) => event.title)}
+ )} +
+ ) + } +} + +export default connect( + (state) => ({ + events: eventsSelector(state), + fetching: state[moduleName].fetching + }), + { getEvents } +)(EventsList) diff --git a/admin/src/components/events/events.css b/admin/src/components/events/events.css new file mode 100644 index 0000000..fc58cbf --- /dev/null +++ b/admin/src/components/events/events.css @@ -0,0 +1,16 @@ +.events-list-item { + box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12); + padding: 24px 16px; + font-family: Arial sans-serif; + margin: 8px; + max-width: 50%; +} + +.events-list-item__title { + font-size: 24px; +} + +.events-list-item__date { + font-size: 12px; + margin-bottom: 12px; +} \ No newline at end of file diff --git a/admin/src/config.js b/admin/src/config.js index a6889dc..413682c 100644 --- a/admin/src/config.js +++ b/admin/src/config.js @@ -5,12 +5,12 @@ import 'firebase/database' export const appName = 'adv-react-25-06' const config = { - apiKey: 'AIzaSyDzqwnZ_39QyqhxYZVPjVH8eBww7DUBmVc', - authDomain: `${appName}.firebaseapp.com`, - databaseURL: `https://${appName}.firebaseio.com`, - projectId: appName, + apiKey: 'AIzaSyD25TAZjq0-smHue8ViLsxv3nWBHm3Pjug', + authDomain: 'advreact-bed03.firebaseapp.com', + databaseURL: 'https://advreact-bed03.firebaseio.com', + projectId: 'advreact-bed03', storageBucket: '', - messagingSenderId: '874599443389' + messagingSenderId: '389238619888' } initializeApp(config) diff --git a/admin/src/ducks/auth.spec.js b/admin/src/ducks/auth.spec.js new file mode 100644 index 0000000..0d06c8a --- /dev/null +++ b/admin/src/ducks/auth.spec.js @@ -0,0 +1,81 @@ +import { call, put, apply } from 'redux-saga/effects' +import firebase from 'firebase/app' +import { + SIGN_UP_REQUEST, + SIGN_UP_SUCCESS, + SIGN_IN_REQUEST, + SIGN_IN_SUCCESS, + signUpSaga, + signInSaga +} from './auth' + +describe('Auth duck sign up saga', () => { + it('should sign up with new email and password', () => { + const auth = firebase.auth() + const credentials = { + email: `test${+new Date()}@test.com`, + password: 'qwerty123' + } + + const action = { + type: SIGN_UP_REQUEST, + payload: credentials + } + + const saga = signUpSaga(action) + + expect(saga.next().value).toEqual( + call( + [auth, auth.createUserWithEmailAndPassword], + credentials.email, + credentials.password + ) + ) + + const user = { + email: credentials.email, + uid: '123' + } + + expect(saga.next(user).value).toEqual( + put({ + type: SIGN_UP_SUCCESS, + payload: { user } + }) + ) + }) + + it('should sign in', () => { + const auth = firebase.auth() + + const credentials = { + email: `test${+new Date()}@test.com`, + password: 'qwerty123' + } + + const action = { + type: SIGN_IN_REQUEST, + payload: credentials + } + + const saga = signInSaga(action) + + expect(saga.next().value).toEqual( + apply(auth, auth.signInWithEmailAndPassword, [ + credentials.email, + credentials.password + ]) + ) + + const user = { + email: credentials.email + } + + expect(saga.next(user).value).toEqual( + put({ + type: SIGN_IN_SUCCESS, + payload: { user } + }) + ) + }) +}) diff --git a/admin/src/ducks/events.js b/admin/src/ducks/events.js new file mode 100644 index 0000000..dfb0e59 --- /dev/null +++ b/admin/src/ducks/events.js @@ -0,0 +1,80 @@ +import { Record, OrderedMap } from 'immutable' +import { put, takeLatest, call } from 'redux-saga/effects' +import { createSelector } from 'reselect' +import { delay } from 'redux-saga' +import firebase from 'firebase/app' + +// Constants +export const moduleName = 'events' +export const GET_EVENTS_REQUEST = `${moduleName}/GET_EVENTS_REQUEST` +export const GET_EVENTS_SUCCESS = `${moduleName}/GET_EVENTS_SUCCESS` +export const GET_EVENTS_ERROR = `${moduleName}/GET_EVENTS_ERROR` + +// Reducer +const ReducerState = Record({ + entities: new OrderedMap(), + fetching: 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 GET_EVENTS_REQUEST: + return state.set('fetching', true) + case GET_EVENTS_SUCCESS: + return state + .set('fetching', false) + .set('entities', eventsResponseToState(payload)) + case GET_EVENTS_ERROR: + return state.set('fetching', false) + default: + return state + } +} + +// Selectors +export const stateSelector = (state) => state[moduleName] +export const eventsSelector = createSelector(stateSelector, (state) => + state.entities.valueSeq().toArray() +) + +// Action Creators +export const getEvents = () => ({ + type: GET_EVENTS_REQUEST +}) + +// Sagas +function* getEventsSaga() { + const ref = firebase.database().ref('/events') + // делей для видимости лоадера + yield call(delay, 1000) + + const snapshot = yield call([ref, ref.once], 'value') + + yield put({ + type: GET_EVENTS_SUCCESS, + payload: snapshot.val() + }) +} + +export function* saga() { + yield takeLatest(GET_EVENTS_REQUEST, getEventsSaga) +} + +// utils +export const eventsResponseToState = (values) => { + return Object.entries(values).reduce( + (item, [uid, value]) => item.set(uid, new EventRecord({ uid, ...value })), + new OrderedMap({}) + ) +} 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/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..4d5ccbb --- /dev/null +++ b/admin/src/routes/events-page.js @@ -0,0 +1,15 @@ +import React, { Component } from 'react' +import EventsList from '../components/events/events-list' + +class EventsPage extends Component { + render() { + return ( +
+

Events list

+ +
+ ) + } +} + +export default EventsPage