Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions admin/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<Route path="/auth" component={Auth} />
<Route path="/events" component={Events} />
<ProtectedRoute path="/admin" component={Admin} />
</div>
)
Expand Down
18 changes: 18 additions & 0 deletions admin/src/components/events/events-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { Component } from 'react'

export default class EventsItem extends Component {
render() {
const { title, url, where, month } = this.props

return (
<li>
<h3>
<a href={url}>{title}</a>
</h3>
<small>
{where} in {month}
</small>
</li>
)
}
}
58 changes: 58 additions & 0 deletions admin/src/components/events/events-list.js
Original file line number Diff line number Diff line change
@@ -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 (
<EventsItem
key={id}
title={title}
url={url}
where={where}
when={when}
month={month}
submissionDeadline={submissionDeadline}
/>
)
})
}

render() {
const { events, isLoading } = this.props

return events && !isLoading ? (
<ul>{this.renderItems()}</ul>
) : (
<div>Loading conferences list</div>
)
}
}

export default connect(
(state) => ({
events: eventsSelector(state),
isLoading: isLoadingSelector(state)
}),
{ fetchEvents }
)(EventsList)
6 changes: 3 additions & 3 deletions admin/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
127 changes: 127 additions & 0 deletions admin/src/ducks/auth.spec.js
Original file line number Diff line number Diff line change
@@ -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++) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше это разбить на несколько тест-кейсов

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)
})
})
114 changes: 114 additions & 0 deletions admin/src/ducks/events.js
Original file line number Diff line number Diff line change
@@ -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({})
)
}
2 changes: 2 additions & 0 deletions admin/src/redux/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
3 changes: 2 additions & 1 deletion admin/src/redux/saga.js
Original file line number Diff line number Diff line change
@@ -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()])
}
16 changes: 16 additions & 0 deletions admin/src/routes/events.js
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h1>Events</h1>
<Route path="/events/list" component={EventsList} />
</div>
)
}
}

export default Events