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
12 changes: 10 additions & 2 deletions admin/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@ import ProtectedRoute from './components/common/protected-route'
import AuthPage from './routes/auth'
import AdminPage from './routes/admin'

const activeStyle = { color: 'red' }

class App extends Component {
render() {
return (
<div>
<div>
<NavLink to="/admin/people" activeStyle={{ color: 'red' }}>
<NavLink to="/admin/events" activeStyle={activeStyle}>
events
</NavLink>
</div>
<div>
<NavLink to="/admin/people" activeStyle={activeStyle}>
people
</NavLink>
</div>
<div>
<NavLink to="/auth/sign-in" activeStyle={{ color: 'red' }}>
<NavLink to="/auth/sign-in" activeStyle={activeStyle}>
auth
</NavLink>
</div>

<Route path="/auth" component={AuthPage} />
<ProtectedRoute path="/admin" component={AdminPage} />
</div>
Expand Down
48 changes: 48 additions & 0 deletions admin/src/components/events/events-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { eventsSelector, getEventsList } from '../../ducks/events'
import { fieldsConfig as eventFields } from './new-event-form'

class EventList extends Component {
static propTypes = {}

componentDidMount() {
this.props.dispatch(getEventsList())
}

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

return (
<div>
{events.length ? (
<table>
<thead>
<tr>
{eventFields.map((field, i) => <th key={i}>{field.name}</th>)}
</tr>
</thead>
<tbody>
{events.map((event, i) => (
<tr key={i}>
Copy link
Owner

Choose a reason for hiding this comment

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

Я б для Event сразу отдельный компонент заводил

Copy link
Author

Choose a reason for hiding this comment

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

Окей, поддерживаю

<td>{event.title}</td>
<td>{event.url}</td>
<td>{event.where}</td>
<td>{event.month}</td>
<td>{event.when}</td>
<td>{event.submissionDeadline}</td>
</tr>
))}
</tbody>
</table>
) : (
<div>The events have not yet been created</div>
)}
</div>
)
}
}

export default connect((state) => ({
events: eventsSelector(state)
}))(EventList)
68 changes: 68 additions & 0 deletions admin/src/components/events/new-event-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { Component } from 'react'
import { reduxForm, Field } from 'redux-form'
import ErrorField from '../common/error-field'

export const fieldsConfig = [
{
name: 'title',
type: 'text'
},
{
name: 'url',
type: 'text'
},
{
name: 'where',
type: 'text'
},
{
name: 'month',
type: 'month'
},
{
name: 'when',
type: 'date'
},
{
name: 'submissionDeadline',
type: 'date'
}
]

class NewEventForm extends Component {
static propTypes = {}

render() {
return (
<div>
<form onSubmit={this.props.handleSubmit}>
{fieldsConfig.map((field, i) => (
<Field
key={i}
name={field.name}
label={field.name}
type={field.type}
component={ErrorField}
/>
))}
<div>
<input type="submit" />
</div>
</form>
</div>
)
}
}

function validate({ title }) {
const errors = {}

if (!title) errors.title = 'title is required'

return errors
}

export default reduxForm({
form: 'event',
validate
})(NewEventForm)
16 changes: 0 additions & 16 deletions admin/src/config.js

This file was deleted.

1 change: 1 addition & 0 deletions admin/src/ducks/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function signIn(email, password) {

export function* signUpSaga({ payload: { email, password } }) {
const auth = firebase.auth()

try {
const user = yield call(
[auth, auth.createUserWithEmailAndPassword],
Expand Down
88 changes: 88 additions & 0 deletions admin/src/ducks/auth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { call, put, take, apply } from 'redux-saga/effects'
import { reset } from 'redux-form'
import firebase from 'firebase/app'
import {
signIn,
signUp,
signUpSaga,
signInSaga,
SIGN_UP_REQUEST,
SIGN_UP_SUCCESS,
SIGN_UP_ERROR,
SIGN_IN_REQUEST,
SIGN_IN_SUCCESS,
SIGN_IN_ERROR,
SIGN_IN_REQUESTS_LIMIT
} from './auth'
import { user } from '../mocks/user'

const auth = firebase.auth()
const { email, password } = user

describe('auth duck', () => {
it('should sign up success', () => {
const sagaProcess = signUpSaga(signUp(email, password))

expect(sagaProcess.next().value).toEqual(
call([auth, auth.createUserWithEmailAndPassword], email, password)
)

expect(sagaProcess.next(user).value).toEqual(
put({ type: SIGN_UP_SUCCESS, payload: { user } })
)

expect(sagaProcess.next().done).toEqual(true)
})
it('should sign up with error', () => {
const error = new Error('sign up with error')
const sagaProcess = signUpSaga(signUp(email, password))

expect(sagaProcess.next().value).toEqual(
call([auth, auth.createUserWithEmailAndPassword], email, password)
)

expect(sagaProcess.throw(error).value).toEqual(
put({ type: SIGN_UP_ERROR, error })
)

expect(sagaProcess.next().done).toEqual(true)
})

it('should sign in success', () => {
const sagaProcess = signInSaga(signIn(email, password))

expect(sagaProcess.next().value).toEqual(take(SIGN_IN_REQUEST))

expect(sagaProcess.next({ payload: { email, password } }).value).toEqual(
apply(auth, auth.signInWithEmailAndPassword, [email, password])
)

expect(sagaProcess.next(user).value).toEqual(
put({ type: SIGN_IN_SUCCESS, payload: { user } })
)

// without counting attempts
})
it('should limit the count of attempts to 3 and throw error', () => {
const error = new Error('sign in with error')
const sagaProcess = signInSaga(signIn(email, password))

for (let i = 0; i < 3; i++) {
expect(sagaProcess.next().value).toEqual(take(SIGN_IN_REQUEST))

expect(sagaProcess.next({ payload: { email, password } }).value).toEqual(
apply(auth, auth.signInWithEmailAndPassword, [email, password])
)

expect(sagaProcess.throw(error).value).toEqual(
put({ type: SIGN_IN_ERROR, error })
)
}

expect(sagaProcess.next().value).toEqual(
put({ type: SIGN_IN_REQUESTS_LIMIT })
)

expect(sagaProcess.next().done).toEqual(true)
})
Copy link
Owner

Choose a reason for hiding this comment

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

Ок, но можно бы и reducer потестить

Copy link
Author

Choose a reason for hiding this comment

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

Хорошо, в следующий раз попробую

})
116 changes: 116 additions & 0 deletions admin/src/ducks/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { appName } from '../config'
import { Record, List } from 'immutable'
import { reset } from 'redux-form'
import firebase from 'firebase/app'
import { createSelector } from 'reselect'
import { takeEvery, all, put, call, apply } from 'redux-saga/effects'
import { generateId } from './utils'

/**
* Constants
* */
export const moduleName = 'events'
const prefix = `${appName}/${moduleName}`
export const ADD_EVENT_REQUEST = `${prefix}/ADD_EVENT_REQUEST`
export const ADD_EVENT = `${prefix}/ADD_EVENT`

export const GET_EVENT_REQUEST = `${prefix}/GET_EVENT_REQUEST`
export const GET_EVENT_SUCCESS = `${prefix}/GET_EVENT_SUCCESS`
export const GET_EVENT_ERROR = `${prefix}/GET_EVENT_ERROR`

/**
* Reducer
* */
const ReducerState = Record({
entities: new List([])
})

const EventRecord = Record({
id: null,
title: null,
url: null,
where: null,
month: null,
when: null,
submissionDeadline: null
})

export default function reducer(state = new ReducerState(), action) {
const { type, payload } = action

switch (type) {
case ADD_EVENT:
return state.update('entities', (entities) =>
entities.unshift(new EventRecord(payload))
)
case GET_EVENT_SUCCESS:
const eventList = Object.keys(payload).map((key) => ({
id: key,
...payload[key]
}))
return state.set(
'entities',
new List(eventList.map((item) => new EventRecord(item)))
)

default:
return state
}
}
/**
* Selectors
* */

export const stateSelector = (state) => state[moduleName]
export const eventsSelector = createSelector(stateSelector, (state) =>
state.entities.valueSeq().toArray()
)

/**
* Action Creators
* */

export function addEvent(event) {
return {
type: ADD_EVENT_REQUEST,
payload: { event }
}
}
export function getEventsList() {
return {
type: GET_EVENT_REQUEST
}
}

/**
* Sagas
* */

export function* addEventSaga({ payload: { event } }) {
const id = yield call(generateId)

yield put({ type: ADD_EVENT, payload: { id, ...event } })
yield put(reset('event'))
}

export function* getEventListSaga() {
const table = firebase.database().ref('events/')

try {
const snapshot = yield apply(table, table.once, ['value'])

yield put({
type: GET_EVENT_SUCCESS,
payload: snapshot.val()
})
} catch (error) {
yield put({ type: GET_EVENT_ERROR, error })
}
}

export function* saga() {
yield all([
takeEvery(ADD_EVENT_REQUEST, addEventSaga),
takeEvery(GET_EVENT_REQUEST, getEventListSaga)
])
}
6 changes: 6 additions & 0 deletions admin/src/ducks/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export function generateId() {
return Date.now() + Math.random()
}

export function generatePassword() {
return Math.random()
.toString(36)
.slice(-8)
}
6 changes: 6 additions & 0 deletions admin/src/mocks/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { generatePassword } from '../ducks/utils'

export const user = {
email: 'test@test.ru',
password: generatePassword()
}
Loading