diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b4863a --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.idea \ No newline at end of file diff --git a/admin-panel/.gitignore b/admin-panel/.gitignore index 5b4863a..7b63ae9 100644 --- a/admin-panel/.gitignore +++ b/admin-panel/.gitignore @@ -20,4 +20,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.idea \ No newline at end of file +.idea +./src/config.js \ No newline at end of file diff --git a/admin-panel/README.md b/admin-panel/README.md index f445bfc..75e4ae5 100644 --- a/admin-panel/README.md +++ b/admin-panel/README.md @@ -1,3 +1,12 @@ ##HT1.1 Завести собственный проект на firebase, работать с ним ##HT1.2 Сделать форму для добавлегния людей(firstName, lastName, email) -##HT1.3 Показывать лоадер и ошибки в форме аутентификации \ No newline at end of file +##HT1.3 Показывать лоадер и ошибки в форме аутентификации + +##HT2.1 Написать тесты на auth +##HT2.2 Чистить форму people после успешного добавления в стор +##HT2.3 Отображать список людей +##HT2.4 Загружать и отображать список ивентов + +##HT3.1 Добавлять людей в firebase +##HT3.2 Реализовать lazy-loading ивентов(InfiniteLoader), ref.orderByKey().startAt(...).limitToFirst(10) +##HT3.3 Тесты на Virtualized Events \ No newline at end of file diff --git a/admin-panel/package.json b/admin-panel/package.json index 5fbeba9..d61a23f 100644 --- a/admin-panel/package.json +++ b/admin-panel/package.json @@ -14,9 +14,11 @@ "react-router-dom": "^4.2.2", "react-router-redux": "^5.0.0-alpha.8", "react-scripts": "1.0.17", + "react-virtualized": "^9.13.0", "redux": "^3.7.2", "redux-form": "^7.2.0", "redux-logger": "^3.0.6", + "redux-saga": "^0.16.0", "redux-thunk": "^2.2.0", "reselect": "^3.0.1" }, @@ -25,5 +27,9 @@ "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" + }, + "devDependencies": { + "enzyme": "^3.2.0", + "enzyme-adapter-react-16": "^1.1.0" } } diff --git a/admin-panel/src/App.js b/admin-panel/src/App.js index fd110f7..d7f8324 100644 --- a/admin-panel/src/App.js +++ b/admin-panel/src/App.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import {Route} from 'react-router-dom' import Auth from './components/routes/auth' import Admin from './components/routes/Admin' +import Users from './components/routes/Users' import ProtectedRoute from './components/common/ProtectedRoute' class App extends Component { @@ -15,6 +16,7 @@ class App extends Component {

Hello world

+ ) } diff --git a/admin-panel/src/Root.js b/admin-panel/src/Root.js index f69da88..7d7223d 100644 --- a/admin-panel/src/Root.js +++ b/admin-panel/src/Root.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import {ConnectedRouter as Router} from 'react-router-redux' import {Provider} from 'react-redux' -import App from './App' +import App from './components/App' import store from './redux' import history from './history' diff --git a/admin-panel/src/components/App.js b/admin-panel/src/components/App.js new file mode 100644 index 0000000..207fb54 --- /dev/null +++ b/admin-panel/src/components/App.js @@ -0,0 +1,31 @@ +import React, { Component } from 'react' +import {Route, NavLink} from 'react-router-dom' +import AuthPage from './routes/auth' +import AdminPage from './routes/Admin' +import ProtectedRoute from './common/ProtectedRoute' +import PersonPage from './routes/PersonPage' +import EventsPage from './routes/EventsPage' + +class App extends Component { + static propTypes = { + + }; + + render() { + return ( +
+

Hello world

+
    +
  • admin
  • +
  • people
  • +
+ + + + +
+ ) + } +} + +export default App \ No newline at end of file diff --git a/admin-panel/src/components/Users/UserForm.js b/admin-panel/src/components/Users/UserForm.js new file mode 100644 index 0000000..b93584b --- /dev/null +++ b/admin-panel/src/components/Users/UserForm.js @@ -0,0 +1,50 @@ +import React, {Component} from 'react' +import {reduxForm, Field} from 'redux-form' +import validator from 'email-validator' +import ErrorField from '../common/ErrorField' + + +class UserForm extends Component { + 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 : "users", + validate +})(UserForm) \ No newline at end of file diff --git a/admin-panel/src/components/Users/index.js b/admin-panel/src/components/Users/index.js new file mode 100644 index 0000000..e1e184c --- /dev/null +++ b/admin-panel/src/components/Users/index.js @@ -0,0 +1,44 @@ +import React, {Component} from 'react' +import UserForm from './UserForm' +import {connect} from 'react-redux' +import {usersSelector, usersLoadingSelector} from '../../ducks/users' + +class Users extends Component { + render() { + return ( +
+

Users page

+ { + this.renderUserList() + } + +
+ ) + } + renderUserList = () => { + let {users, isLoading} = this.props; + if (isLoading) return
Uploading new user...
+ return ( + users.length ? +
    + { + users.map(u=>( +
  • +

    First Name: {u.firstName}

    +

    Last Name: {u.lastName}

    +

    email: {u.email}

    +
  • + )) + } +
: +

no user

+ ) + } +} + +export default connect( + state => ({ + users : usersSelector(state), + isLoading : usersLoadingSelector(state), + }) +)(Users) \ No newline at end of file diff --git a/admin-panel/src/components/auth/SignInForm.js b/admin-panel/src/components/auth/SignInForm.js index 3033ef6..df7f35b 100644 --- a/admin-panel/src/components/auth/SignInForm.js +++ b/admin-panel/src/components/auth/SignInForm.js @@ -1,5 +1,9 @@ import React, { Component } from 'react' +import {connect} from 'react-redux' import {reduxForm, Field} from 'redux-form' +import ErrorField from '../common/ErrorField' +import Loader from '../common/Loader' +import {loadingSelector, errorSelector} from '../../ducks/auth' class SignInForm extends Component { static propTypes = { @@ -7,24 +11,25 @@ class SignInForm extends Component { }; render() { + const {loading, authError} = this.props return (

Sign In

-
- email: -
-
- password: -
+ + + {authError &&

{authError}

} + {loading && } -
) } } -export default reduxForm({ +export default connect(state => ({ + loading: loadingSelector(state), + authError: errorSelector(state) +}))(reduxForm({ form: 'auth' -})(SignInForm) \ No newline at end of file +})(SignInForm)) \ No newline at end of file diff --git a/admin-panel/src/components/auth/SignUpForm.js b/admin-panel/src/components/auth/SignUpForm.js index d8e9a85..20b0acf 100644 --- a/admin-panel/src/components/auth/SignUpForm.js +++ b/admin-panel/src/components/auth/SignUpForm.js @@ -1,7 +1,10 @@ import React, { Component } from 'react' +import {connect} from 'react-redux' import {reduxForm, Field} from 'redux-form' import validator from 'email-validator' import ErrorField from '../common/ErrorField' +import Loader from '../common/Loader' +import {loadingSelector, errorSelector} from '../../ducks/auth' class SignUpForm extends Component { static propTypes = { @@ -9,16 +12,15 @@ class SignUpForm extends Component { }; render() { + const {loading, authError} = this.props return (

Sign In

-
- email: -
-
- password: -
+ + + {authError &&

{authError}

} + {loading && } @@ -39,7 +41,10 @@ const validate = ({ email, password }) => { return errors } -export default reduxForm({ +export default connect(state => ({ + loading: loadingSelector(state), + authError: errorSelector(state) +}))(reduxForm({ form: 'auth', validate -})(SignUpForm) \ No newline at end of file +})(SignUpForm)) \ No newline at end of file diff --git a/admin-panel/src/components/common/ErrorField.js b/admin-panel/src/components/common/ErrorField.js index e9b16d5..50fe711 100644 --- a/admin-panel/src/components/common/ErrorField.js +++ b/admin-panel/src/components/common/ErrorField.js @@ -1,20 +1,15 @@ -import React, { Component } from 'react' - -class ErrorField extends Component { - static propTypes = { - - }; - - render() { - const {input, meta: { error, touched }, type} = this.props - const errorMessage = error && touched &&

{error}

- return ( -
- - {errorMessage} -
- ) - } +import React from 'react' + +function ErrorField(props) { + const {label, input, type, meta: {error, touched}} = props + const errorText = touched && error &&
{error}
+ return ( +
+ + + {errorText} +
+ ) } export default ErrorField \ No newline at end of file diff --git a/admin-panel/src/components/common/Loader.js b/admin-panel/src/components/common/Loader.js new file mode 100644 index 0000000..9418787 --- /dev/null +++ b/admin-panel/src/components/common/Loader.js @@ -0,0 +1,12 @@ +import React from 'react' + +function Loader() { + return ( +

Loading...

+ ) +} + +Loader.propTypes = { +} + +export default Loader \ No newline at end of file diff --git a/admin-panel/src/components/events/EventsTable.js b/admin-panel/src/components/events/EventsTable.js new file mode 100644 index 0000000..432e7a1 --- /dev/null +++ b/admin-panel/src/components/events/EventsTable.js @@ -0,0 +1,41 @@ +import React, { Component } from 'react' +import {connect} from 'react-redux' +import {fetchAllEvents, selectEvent, eventListSelector, loadedSelector, loadingSelector} from '../../ducks/events' +import Loader from '../common/Loader' + +export class EventsTable extends Component { + static propTypes = { + + }; + + componentDidMount() { + this.props.fetchAllEvents() + } + + render() { + if (this.props.loading) return + return ( + + + {this.getRows()} + +
+ ) + } + + getRows = () => this.props.events.map(this.getRow) + + getRow = (event) => ( + this.props.selectEvent(event.uid)}> + {event.title} + {event.when} + {event.where} + + ) +} + +export default connect((state) => ({ + events: eventListSelector(state), + loading: loadingSelector(state), + loaded: loadedSelector(state) +}), { fetchAllEvents, selectEvent })(EventsTable) \ No newline at end of file diff --git a/admin-panel/src/components/events/EventsTable.test.js b/admin-panel/src/components/events/EventsTable.test.js new file mode 100644 index 0000000..4cf75c1 --- /dev/null +++ b/admin-panel/src/components/events/EventsTable.test.js @@ -0,0 +1,48 @@ +import React from 'react' +import {shallow} from 'enzyme' +import events from '../../mocks/conferences' + +import {EventsTable} from './EventsTable' +import Loader from '../common/Loader' + +const eventList = events.map(event => ({...event, uid: Math.random()})) + +describe('Events Table', () => { + it('should render a loader', () => { + const table = shallow(, { disableLifecycleMethods: true }) + + expect(table.contains()) + }); + + it('should render N rows', () => { + const table = shallow(, { disableLifecycleMethods: true }) + + expect(table.find('.test__event_table_row').length).toBe(eventList.length) + }); + + it('should request fetch all events', function (done) { + shallow( done()} + />) + }); + + it('should select an event', () => { + let selected = null + + const table = shallow( selected = uid} + />, { disableLifecycleMethods: true }) + + table.find('.test__event_table_row').first().simulate('click') + + expect(selected).toEqual(eventList[0].uid) + + }); + +}); \ No newline at end of file diff --git a/admin-panel/src/components/events/EventsTableVirtualized.js b/admin-panel/src/components/events/EventsTableVirtualized.js new file mode 100644 index 0000000..c4f8c3e --- /dev/null +++ b/admin-panel/src/components/events/EventsTableVirtualized.js @@ -0,0 +1,55 @@ +import React, { Component } from 'react' +import {connect} from 'react-redux' +import {fetchAllEvents, selectEvent, eventListSelector, loadedSelector, loadingSelector} from '../../ducks/events' +import Loader from '../common/Loader' +import {Table, Column} from 'react-virtualized' +import 'react-virtualized/styles.css' + +export class EventsTableVirtualized extends Component { + static propTypes = { + + }; + + componentDidMount() { + this.props.fetchAllEvents() + } + + render() { + if (this.props.loading) return + return ( + + + + +
+ ) + } + + rowGetter = ({ index }) => this.props.events[index] +} + +export default connect((state) => ({ + events: eventListSelector(state), + loading: loadingSelector(state), + loaded: loadedSelector(state) +}), { fetchAllEvents, selectEvent })(EventsTableVirtualized) \ No newline at end of file diff --git a/admin-panel/src/components/people/NewPersonForm.js b/admin-panel/src/components/people/NewPersonForm.js new file mode 100644 index 0000000..6707eb7 --- /dev/null +++ b/admin-panel/src/components/people/NewPersonForm.js @@ -0,0 +1,40 @@ +import React, { Component } from 'react' +import {reduxForm, Field} from 'redux-form' +import validateEmail from 'email-validator' +import ErrorField from '../common/ErrorField' + +class NewPersonForm extends Component { + static propTypes = { + + }; + + render() { + return ( +
+
+ + + +
+ +
+ +
+ ) + } +} + +function validate({firstName, email}) { + const errors = {} + if (!firstName) errors.firstName = 'first name is required' + + if (!email) errors.email = 'email is required' + else if (!validateEmail.validate(email)) errors.email = 'email is invalid' + + return errors +} + +export default reduxForm({ + form: 'person', + validate +})(NewPersonForm) \ No newline at end of file diff --git a/admin-panel/src/components/people/PeopleList.js b/admin-panel/src/components/people/PeopleList.js new file mode 100644 index 0000000..1fe2549 --- /dev/null +++ b/admin-panel/src/components/people/PeopleList.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react' +import {connect} from 'react-redux' +import {peopleListSelector} from '../../ducks/people' +import {List} from 'react-virtualized' +import 'react-virtualized/styles.css' + +class PeopleList extends Component { + static propTypes = { + + }; + + render() { + return + + } + + rowRenderer = ({ style, index, key }) => { + const person = this.props.people[index] + return ( +
+

{person.firstName} {person.lastName}

+

{person.email}

+
+ ) + } + + getRow = (person) => ( + + {person.firstName} + {person.lastName} + {person.email} + + ) +} + +export default connect((state) => ({ + people: peopleListSelector(state) +}))(PeopleList) \ No newline at end of file diff --git a/admin-panel/src/components/people/PeopleTable.js b/admin-panel/src/components/people/PeopleTable.js new file mode 100644 index 0000000..c658acf --- /dev/null +++ b/admin-panel/src/components/people/PeopleTable.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import {connect} from 'react-redux' +import {peopleListSelector} from '../../ducks/people' + +class PeopleTable extends Component { + static propTypes = { + + }; + + render() { + return ( + + + {this.getRows()} + +
+ ) + } + + getRows = () => this.props.people.map(this.getRow) + + getRow = (person) => ( + + {person.firstName} + {person.lastName} + {person.email} + + ) +} + +export default connect((state) => ({ + people: peopleListSelector(state) +}))(PeopleTable) \ No newline at end of file diff --git a/admin-panel/src/components/routes/EventsPage.js b/admin-panel/src/components/routes/EventsPage.js new file mode 100644 index 0000000..eedf79d --- /dev/null +++ b/admin-panel/src/components/routes/EventsPage.js @@ -0,0 +1,18 @@ +import React, { Component } from 'react' +import EventsTableVirtualized from '../events/EventsTableVirtualized' + +class EventsPage extends Component { + static propTypes = { + + }; + + render() { + return ( +
+ +
+ ) + } +} + +export default EventsPage \ No newline at end of file diff --git a/admin-panel/src/components/routes/PersonPage.js b/admin-panel/src/components/routes/PersonPage.js new file mode 100644 index 0000000..9bf8df9 --- /dev/null +++ b/admin-panel/src/components/routes/PersonPage.js @@ -0,0 +1,23 @@ +import React, { Component } from 'react' +import {connect} from 'react-redux' +import {addPerson} from '../../ducks/people' +import NewPersonForm from '../people/NewPersonForm' +import PeopleList from '../people/PeopleList' + +class PersonPage extends Component { + static propTypes = { + + }; + + render() { + return ( +
+

Add new person

+ + +
+ ) + } +} + +export default connect(null, {addPerson})(PersonPage) \ No newline at end of file diff --git a/admin-panel/src/components/routes/Users.js b/admin-panel/src/components/routes/Users.js new file mode 100644 index 0000000..82dd334 --- /dev/null +++ b/admin-panel/src/components/routes/Users.js @@ -0,0 +1,24 @@ +import React, { Component } from 'react' +import {Route, NavLink} from 'react-router-dom' +import {connect} from 'react-redux' +import {addUser} from '../../ducks/users' +import UsersList from '../Users' + +class Users extends Component { + render() { + return ( +
+

Users page

+
    +
  • Users list
  • +
+ } /> +
+ ) + } + + onAddUser = ({firstName, lastName, email}) => this.props.addUser({firstName, lastName, email}) + +} + +export default connect(null, { addUser })(Users) \ No newline at end of file diff --git a/admin-panel/src/config.js b/admin-panel/src/config.js index 65bba00..a24ff65 100644 --- a/admin-panel/src/config.js +++ b/admin-panel/src/config.js @@ -1,14 +1,14 @@ import firebase from 'firebase' -export const appName = 'advreact-04-12' +export const appName = 'react-adv' const config = { - apiKey: "AIzaSyCmDWlgYIhtEr1pWjgKYds3iXKWBl9wbjE", + apiKey: "AIzaSyAs8-oh5yTEUxC5KQSOSZpaE8xReLZk0qQ", authDomain: `${appName}.firebaseapp.com`, databaseURL: `https://${appName}.firebaseio.com`, projectId: appName, - storageBucket: "", - messagingSenderId: "95255462276" + storageBucket: `${appName}.appspot.com`, + messagingSenderId: "857981968367" } firebase.initializeApp(config) \ No newline at end of file diff --git a/admin-panel/src/ducks/auth.js b/admin-panel/src/ducks/auth.js index f031871..92ed143 100644 --- a/admin-panel/src/ducks/auth.js +++ b/admin-panel/src/ducks/auth.js @@ -1,6 +1,9 @@ +import {all, takeEvery, take, put, apply, call} from 'redux-saga/effects' import {appName} from '../config' +import {createSelector} from 'reselect' import {Record} from 'immutable' import firebase from 'firebase' +import {replace} from 'react-router-redux' /** * Constants @@ -8,10 +11,15 @@ import firebase from 'firebase' export const moduleName = 'auth' const prefix = `${appName}/${moduleName}` +export const SIGN_IN_REQUEST = `${prefix}/SIGN_IN_REQUEST` export const SIGN_IN_START = `${prefix}/SIGN_IN_START` export const SIGN_IN_SUCCESS = `${prefix}/SIGN_IN_SUCCESS` +export const SIGN_IN_ERROR = `${prefix}/SIGN_IN_ERROR` + +export const SIGN_UP_REQUEST = `${prefix}/SIGN_UP_REQUEST` export const SIGN_UP_START = `${prefix}/SIGN_UP_START` export const SIGN_UP_SUCCESS = `${prefix}/SIGN_UP_SUCCESS` +export const SIGN_UP_ERROR = `${prefix}/SIGN_UP_ERROR` /** * Reducer @@ -28,13 +36,22 @@ export default function reducer(state = new ReducerRecord(), action) { switch (type) { case SIGN_IN_START: case SIGN_UP_START: - return state.set('loading', true) + return state + .set('error', null) + .set('loading', true) case SIGN_IN_SUCCESS: case SIGN_UP_SUCCESS: return state .set('loading', false) .set('user', payload.user) + + case SIGN_IN_ERROR: + case SIGN_UP_ERROR: + return state + .set('loading', false) + .set('error', payload.error.message) + default: return state } @@ -44,43 +61,94 @@ export default function reducer(state = new ReducerRecord(), action) { * Selectors * */ -export const userSelector = state => state[moduleName].user +export const stateSelector = state => state[moduleName] +export const userSelector = createSelector(stateSelector, state => state.user) +export const errorSelector = createSelector(stateSelector, state => state.error) +export const loadingSelector = createSelector(stateSelector, state => state.loading) /** * Action Creators * */ -export function signIn(email, password) { - return (dispatch) => { - dispatch({ - type: SIGN_IN_START - }) - - firebase.auth().signInWithEmailAndPassword(email, password) - .then(user => dispatch({ - type: SIGN_IN_SUCCESS, - payload: { user } - })) +export function signUp(email, password) { + return { + type: SIGN_UP_REQUEST, + payload: { email, password } } } -export function signUp(email, password) { - return (dispatch) => { - dispatch({ - type: SIGN_UP_START - }) - firebase.auth().createUserWithEmailAndPassword(email, password) - .then(user => dispatch({ - type: SIGN_UP_SUCCESS, - payload: { user } - })) +export function signIn(email, password) { + return { + type: SIGN_IN_REQUEST, + payload: { email, password } } } + firebase.auth().onAuthStateChanged(user => { if (user) window.store.dispatch({ type: SIGN_IN_SUCCESS, payload: { user } }) -}) \ No newline at end of file +}) + +/** + * Sagas + */ + +export const signUpSaga = function * () { + while (true) { + const action = yield take(SIGN_UP_REQUEST) + const {email, password} = action.payload + + yield put({ + type: SIGN_UP_START + }) + + try { + const auth = firebase.auth() + yield call([auth, auth.createUserWithEmailAndPassword], email, password) + } catch (error) { + yield put({ + type: SIGN_UP_ERROR, + payload: {error} + }) + } + } +} + +export const signInSaga = function * (action) { + const { email, password } = action.payload + + yield put({ + type: SIGN_IN_START + }) + + try { + const auth = firebase.auth() + yield apply(auth, auth.signInWithEmailAndPassword, [email, password]) + } catch (error) { + yield put({ + type: SIGN_IN_ERROR, + payload: { error } + }) + } +} + +export function * watchStatusChangeSaga() { + while (true) { + yield take(SIGN_IN_SUCCESS) + + yield (put(replace('/events'))) + } +} + + +export const saga = function * () { + yield all([ + takeEvery(SIGN_IN_REQUEST, signInSaga), + signUpSaga(), + watchStatusChangeSaga() + ]) +} \ No newline at end of file diff --git a/admin-panel/src/ducks/auth.test.js b/admin-panel/src/ducks/auth.test.js new file mode 100644 index 0000000..ab502c2 --- /dev/null +++ b/admin-panel/src/ducks/auth.test.js @@ -0,0 +1,110 @@ +import firebase from 'firebase' +import reducer, { + signUpSaga, signInSaga, watchStatusChangeSaga, + SIGN_UP_REQUEST, SIGN_UP_START, SIGN_UP_SUCCESS, SIGN_UP_ERROR, + SIGN_IN_REQUEST, SIGN_IN_START, SIGN_IN_SUCCESS, SIGN_IN_ERROR, + ReducerRecord +} from './auth' +import {take, call, put} from 'redux-saga/effects' +import {replace} from 'react-router-redux' + +/** + * Saga tests + * */ + +it('should sign up', () => { + const saga = signUpSaga() + const auth = firebase.auth() + const authData = { + email: 'lala@example.com', + password: '12341234' + } + + const user = { + email: authData.email, + uid: Math.random().toString() + } + + const requestAction = { + type: SIGN_UP_REQUEST, + payload: authData + } + + expect(saga.next().value).toEqual(take(SIGN_UP_REQUEST)) + + expect(saga.next(requestAction).value).toEqual(put({type: SIGN_UP_START})) + + expect(saga.next(requestAction).value).toEqual(call( + [auth, auth.createUserWithEmailAndPassword], + authData.email, authData.password + )) + + const error = new Error + + expect(saga.throw(error).value).toEqual(put({ + type: SIGN_UP_ERROR, + payload: {error} + })) +}) + +it('should sign in', () => { + const auth = firebase.auth() + const authData = { + email: 'lala@example.com', + password: '12341234' + } + + const user = { + email: authData.email, + uid: Math.random().toString() + } + + const requestAction = { + type: SIGN_IN_REQUEST, + payload: authData + } + + const saga = signInSaga(requestAction) + + expect(saga.next().value).toEqual(put({type: SIGN_IN_START})) + + expect(saga.next().value).toEqual(call( + [auth, auth.signInWithEmailAndPassword], + authData.email, authData.password + )) + + const error = new Error + + expect(saga.throw(error).value).toEqual(put({ + type: SIGN_IN_ERROR, + payload: {error} + })) +}) + +it('should redirect', () => { + const saga = watchStatusChangeSaga() + + expect(saga.next().value).toEqual(take(SIGN_IN_SUCCESS)) + + expect(saga.next().value).toEqual(put(replace('/events'))) +}) + +/** + * Reducer Tests + * */ + +it('should sign in', () => { + const state = new ReducerRecord() + const user = { + email: 'lala@example.com', + uid: Math.random().toString() + } + + const newState = reducer(state, { + type: SIGN_IN_SUCCESS, + payload: {user} + }) + + expect(newState).toEqual(new ReducerRecord({user})) +}) + diff --git a/admin-panel/src/ducks/events.js b/admin-panel/src/ducks/events.js new file mode 100644 index 0000000..f13e342 --- /dev/null +++ b/admin-panel/src/ducks/events.js @@ -0,0 +1,116 @@ +import {all, takeEvery, put, call} from 'redux-saga/effects' +import {appName} from '../config' +import {Record, OrderedSet, OrderedMap} from 'immutable' +import firebase from 'firebase' +import {createSelector} from 'reselect' +import {fbToEntities} from './utils' + +/** + * Constants + * */ +export const moduleName = 'events' +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 SELECT = `${prefix}/SELECT` + +/** + * Reducer + * */ +export const ReducerRecord = Record({ + loading: false, + loaded: false, + selected: new OrderedSet(), + entities: new OrderedMap({}) +}) + +export const EventRecord = Record({ + uid: null, + month: null, + submissionDeadline: null, + title: null, + url: null, + when: null, + where: null +}) + +export default function reducer(state = new ReducerRecord(), action) { + const {type, payload} = action + + switch (type) { + 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, EventRecord)) + + case SELECT: + return state.update('selected', selected => selected.has(payload.uid) + ? selected.remove(payload.uid) + : selected.add(payload.uid) + ) + + default: + return state + } +} + +/** + * Selectors + * */ + +export const stateSelector = state => state[moduleName] +export const entitiesSelector = createSelector(stateSelector, state => state.entities) +export const loadingSelector = createSelector(stateSelector, state => state.loading) +export const loadedSelector = createSelector(stateSelector, state => state.loaded) +export const eventListSelector = createSelector(entitiesSelector, entities => entities.valueSeq().toArray()) + +/** + * Action Creators + * */ + +export function fetchAllEvents() { + return { + type: FETCH_ALL_REQUEST + } +} + +export function selectEvent(uid) { + return { + type: SELECT, + payload: { uid } + } +} + +/** + * Sagas + * */ + +export function* fetchAllSaga() { + const ref = firebase.database().ref('events') + + yield put({ + type: FETCH_ALL_START + }) + + const snapshot = yield call([ref, ref.once], 'value') + + console.log('---', snapshot) + + yield put({ + type: FETCH_ALL_SUCCESS, + payload: snapshot.val() + }) +} + +export function* saga() { + yield all([ + takeEvery(FETCH_ALL_REQUEST, fetchAllSaga) + ]) +} \ No newline at end of file diff --git a/admin-panel/src/ducks/people.js b/admin-panel/src/ducks/people.js new file mode 100644 index 0000000..9a5f996 --- /dev/null +++ b/admin-panel/src/ducks/people.js @@ -0,0 +1,81 @@ +import {appName} from '../config' +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' + +/** + * Constants + * */ +export const moduleName = 'people' +const prefix = `${appName}/${moduleName}` +export const ADD_PERSON = `${prefix}/ADD_PERSON` +export const ADD_PERSON_SUCCESS = `${prefix}/ADD_PERSON_SUCCESS` + +/** + * Reducer + * */ +const ReducerState = Record({ + entities: new OrderedMap({}) +}) + +const PersonRecord = Record({ + id: null, + firstName: null, + lastName: null, + email: null +}) + +export default function reducer(state = new ReducerState(), action) { + const {type, payload} = action + + switch (type) { + case ADD_PERSON_SUCCESS: + return state.setIn(['entities', payload.uid],new PersonRecord(payload)) + + default: + return state + } +} + +/** + * Selectors + * */ +export const stateSelector = state => state[moduleName] +export const entitiesSelector = createSelector(stateSelector, state => state.entities) +export const peopleListSelector = createSelector(entitiesSelector, entities => entities.valueSeq().toArray()) + +/** + * Action Creators + * */ + +export function addPerson(person) { + return { + type: ADD_PERSON, + payload: { person } + } +} + +/** + * Sagas + */ + +export const addPersonSaga = function * (action) { + const { person } = action.payload + + const uid = yield call(generateId) + + yield put({ + type: ADD_PERSON_SUCCESS, + payload: {uid, ...person} + }) + + yield put(reset('person')) +} + +export const saga = function * () { + yield all([ + takeEvery(ADD_PERSON, addPersonSaga) + ]) +} \ No newline at end of file diff --git a/admin-panel/src/ducks/people.test.js b/admin-panel/src/ducks/people.test.js new file mode 100644 index 0000000..d24f765 --- /dev/null +++ b/admin-panel/src/ducks/people.test.js @@ -0,0 +1,35 @@ +import {call, put} from 'redux-saga/effects' +import {addPersonSaga, ADD_PERSON, ADD_PERSON_SUCCESS} from './people' +import {reset} from 'redux-form' +import {generateId} from './utils' + +describe('people saga', () => { + it('should add person', () => { + const person = { + firstName: 'Roman', + lastName: 'Iakobchuk', + email: 'r.iakobchuk@javascript.ru' + } + + const action = { + type: ADD_PERSON, + payload: { person } + } + + const generator = addPersonSaga(action) + + expect(generator.next().value).toEqual(call(generateId)) + + const uid = generateId() + + expect(generator.next(uid).value).toEqual(put({ + type: ADD_PERSON_SUCCESS, + payload: {uid, ...person} + })) + + expect(generator.next().value).toEqual(put(reset('person'))) + + expect(generator.next().done).toBe(true) + + }); +}); \ No newline at end of file diff --git a/admin-panel/src/ducks/users.js b/admin-panel/src/ducks/users.js new file mode 100644 index 0000000..5ad3ac8 --- /dev/null +++ b/admin-panel/src/ducks/users.js @@ -0,0 +1,93 @@ +import {appName} from '../config' +import {Record} from 'immutable' +import {arrayToMap} from '../helpers' +import {createSelector} from 'reselect' + +/** + * Constants + * */ +export const moduleName = 'users' +const prefix = `${appName}/${moduleName}` + +export const ADD_USER_START = `${prefix}/_START` +export const ADD_USER_SUCCESS = `${prefix}/_SUCCESS` + +/** + * Reducer + * */ + +const UsersListRecord = Record({ + id : null, + firstName : null, + lastName : null, + email : null, +}) + +const ReducerRecord = Record({ + entities : arrayToMap([], UsersListRecord), + loading : false, + loaded : false, +}) + +const defaultState = new ReducerRecord(); + +export default function reducer(state = defaultState, action) { + const {type, payload} = action + + switch (type) { + case ADD_USER_START : { + return state.set('loading', true); + } + case ADD_USER_SUCCESS : { + let {randomId : id, firstName, lastName, email} = payload; + state = state.set('loading', false); + return state.setIn(['entities', id], new UsersListRecord({ + id, + firstName, + lastName, + email, + })) + } + default: + return state + } +} + +/** + * Selectors + * */ + +export const usersMapSelector = state => state[moduleName].entities + +export const usersSelector = createSelector(usersMapSelector, usersMap => usersMap.valueSeq().toArray()) + +export const usersLoadingSelector = state => state[moduleName].loading + +/** + * Action Creators + * */ + +export const addUser = (params) => { + let {firstName, lastName, email} = params; + return (dispatch) => { + dispatch({ + type : ADD_USER_START, + }); + new Promise((resolve) => { + setTimeout(() => { + resolve() + }, 3000) + }).then(() => { + dispatch({ + type : ADD_USER_SUCCESS, + payload : { + generateId : true, + firstName, + lastName, + email + } + }) + }) + + } +} \ No newline at end of file diff --git a/admin-panel/src/ducks/utils.js b/admin-panel/src/ducks/utils.js new file mode 100644 index 0000000..df63cc2 --- /dev/null +++ b/admin-panel/src/ducks/utils.js @@ -0,0 +1,13 @@ +import {OrderedMap} from 'immutable' + +export function generateId() { + return Date.now() +} + +export function fbToEntities(values, DataRecord) { + return Object.entries(values) + .reduce( + (acc, [uid, value]) => acc.set(uid, new DataRecord({ uid, ...value })), + new OrderedMap({}) + ) +} \ No newline at end of file diff --git a/admin-panel/src/helpers/index.js b/admin-panel/src/helpers/index.js new file mode 100644 index 0000000..12f9b51 --- /dev/null +++ b/admin-panel/src/helpers/index.js @@ -0,0 +1,10 @@ +import {Map} from 'immutable'; + +export let arrayToMap = (arr, ItemRecord, idKey = 'id') => { + let res = arr.reduce(function (acc, item) { + return acc.set(item[idKey], ItemRecord ? new ItemRecord(item) : item); + }, new Map({})); + return res; +} + +export let getRandomId = () => (Date.now() + Math.random()).toString() \ No newline at end of file diff --git a/admin-panel/src/index.js b/admin-panel/src/index.js index ad60938..adb21d9 100644 --- a/admin-panel/src/index.js +++ b/admin-panel/src/index.js @@ -2,5 +2,6 @@ import React from 'react' import ReactDOM from 'react-dom' import './config' import Root from './Root' +import './mocks' ReactDOM.render(, document.getElementById('root')) diff --git a/admin-panel/src/mocks/conferences.js b/admin-panel/src/mocks/conferences.js new file mode 100644 index 0000000..a5bea14 --- /dev/null +++ b/admin-panel/src/mocks/conferences.js @@ -0,0 +1,2384 @@ +export default [ + { + "title": "Agent Conf", + "url": "http://www.agent.sh/", + "where": "Dornbirn, Austria", + "when": "January 20-21, 2017", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "O'Reilly Velocity Conference", + "url": "http://conferences.oreilly.com/velocity/vl-ca", + "where": "San Jose, CA", + "when": "January 19-22, 2017", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "Script17", + "url": "https://scriptconf.org/", + "where": "Linz, Austria", + "when": "January 27, 2017", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "Agile Content Conf", + "url": "https://2017.agilecontentconf.com/", + "where": "London, UK", + "when": "January 30-31, 2017", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "Jfokus", + "url": "http://www.jfokus.se/jfokus/", + "where": "Stockholm, Sweden", + "when": "February 6-8, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Webstock", + "url": "http://www.webstock.org.nz/17/", + "where": "Wellington, New Zealand", + "when": "February 13-17, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Sustainable UX", + "url": "http://sustainableux.com/", + "where": "Online", + "when": "February 16, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "The Rolling Scopes Conference", + "url": "https://2017.conf.rollingscopes.com/", + "where": "Minsk, Belarus", + "when": "February 18-19, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "The Lead Developer New York", + "url": "http://2017.theleaddeveloper-ny.com/", + "where": "New York City, US", + "when": "February 21, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days - Zurich", + "url": "https://voxxeddays.com/zurich/", + "where": "Zurich, Switzerland", + "when": "February 23, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "UX Riga", + "url": "http://www.uxriga.lv/", + "where": "Riga, Latvia", + "when": "February 23, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Typography Day 2017", + "url": "http://www.typoday.in/", + "where": "Moratuwa, Sri Lanka", + "when": "February 23, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days - Cern", + "url": "https://voxxeddays.com/cern/", + "where": "Geneva, Switzerland", + "when": "February 25, 2017", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Forward JS", + "url": "https://forwardjs.com/", + "where": "San Francisco, CA", + "when": "March 1st, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days - Bristol", + "url": "https://voxxeddays.com/bristol/", + "where": "Watershed, Bristol", + "when": "March 2nd, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "jDays", + "url": "http://www.jdays.se/", + "where": "Göteborg, Sweden", + "when": "March 7-8, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "A Day of REST", + "url": "https://adayofrest.hm/boston-2017/", + "where": "Boston, MA", + "when": "March 9th, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days - Bucharest", + "url": "https://voxxeddays.com/bucharest/", + "where": "Bucharest, Romania", + "when": "March 10th, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "SXSW", + "url": "https://www.sxsw.com", + "where": "Austin, Texas", + "when": "March 10-19, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "React Conf", + "url": "http://conf.reactjs.org/", + "where": "Santa Clara, CA", + "when": "March 13th-14th, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "SmashingConf Oxford 2017", + "url": "https://shop.smashingmagazine.com/products/smashingconf-oxford-2017", + "where": "Oxford, England", + "when": "March 14th–15th, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "JS Remote Conf", + "url": "https://devchat.tv/conferences/js-remote-conf-2017", + "where": "Online", + "when": "March 15th–16th, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "NG-NL", + "url": "http://ng-nl.org/", + "where": "Amsterdam, Netherlands", + "when": "March 16, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days - Vienna", + "url": "https://voxxeddays.com/vienna/", + "where": "Vienna, Austria", + "when": "March 16-17, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Wroc_love.rb - Wrocław", + "url": "http://www.wrocloverb.com/", + "where": "Wrocław, Poland", + "when": "March 17th-19th, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "JazzCon.Tech", + "url": "http://www.jazzcon.tech/", + "where": "New Orleans, USA", + "when": "March 20th-22nd, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "JSUnconf", + "url": "http://2017.jsunconf.eu/", + "where": "Hamburg, Germany", + "when": "March 25th-26th, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "React London", + "url": "https://react.london/", + "where": "London, UK", + "when": "March 28, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Ember Conf", + "url": "http://emberconf.com/", + "where": "Portland, OR", + "when": "March 28-29, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Render Conf 2017", + "url": "http://2017.render-conf.com/", + "where": "Oxford, England", + "when": "March 30-31, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Design it; Build it", + "url": "http://www.dibiconference.com", + "where": "Edinburgh, Scotland", + "when": "March 30-31, 2017", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "AlterConf", + "url": "https://www.alterconf.com/conferences/london-england", + "where": "London, UK", + "when": "April 1, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "An Event Apart", + "url": "https://aneventapart.com/event/seattle-2017", + "where": "Seattle, WA", + "when": "April 3-5, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "26th International World Wide Web Conference, 2017", + "url": "http://www.www2017.com.au/", + "where": "Perth, Australia", + "when": "April 3-7, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "SmashingConf San Francisco 2017", + "url": "https://shop.smashingmagazine.com/products/smashingconf-san-francisco-2017", + "where": "San Francisco, United States", + "when": "April 4th–5th, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "ng-conf 2017", + "url": "https://www.ng-conf.org/", + "where": "Salt Lake City, Utah", + "when": "April 5-7, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Bulgaria Web Summit 2017", + "url": "https://bulgariawebsummit.com", + "where": "Sofia, Bulgaria", + "when": "April 7th–8th, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "ForwardJS: Ottawa", + "url": "https://forwardjs.com/ottawa", + "where": "Ottawa, Ontario Canada", + "when": "April 6th–8th, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Front End Design Conference", + "url": "https://frontenddesignconference.com/", + "where": "St. Petersburg, FL", + "when": "April 19-21, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "React Amsterdam", + "url": "http://react-amsterdam.com/", + "where": "Amsterdam, NL", + "when": "April 21, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "FITC Toronto", + "url": "http://fitc.ca/event/to17/", + "where": "Toronto, Canada", + "when": "April 23-25, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Open Vis Conf", + "url": "https://openvisconf.com/", + "where": "Boston, MA", + "when": "April 24-25, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Serverlessconf", + "url": "https://austin.serverlessconf.io/", + "where": "Austin, TX", + "when": "April 26-28, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Generate", + "url": "https://www.generateconf.com/new-york-2017/", + "where": "New York City, NY", + "when": "April 27-28, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "CSSConf EU", + "url": "http://2017.cssconf.eu", + "where": "Arena Berlin, Germany", + "when": "May 5, 2017", + "month": "May", + "submissionDeadline": "Jan 1st, 2017" + }, + { + "title": "Voxxed Days - Ticino", + "url": "https://voxxeddays.com/ticino/", + "where": "Palazzo dei Congressi, Lugano", + "when": "May 6, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "JSConf EU", + "url": "http://2017.jsconf.eu", + "where": "Arena Berlin, Germany", + "when": "May 6-7, 2017", + "month": "May", + "submissionDeadline": "Jan 1st, 2017" + }, + { + "title": "OSCON", + "url": "http://conferences.oreilly.com/oscon/oscon-tx", + "where": "Austin, TX", + "when": "May 8-11, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Front", + "url": "https://www.frontutah.com/", + "where": "Salt Lake City, Utah", + "when": "May 9-10, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Angular Summit", + "url": "https://angularsummit.com/conference/chicago/2017/05/home", + "where": "Chicago, IL", + "when": "May 9-11, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Riga Dev Days", + "url": "http://rigadevdays.lv/", + "where": "Riga, Latvia", + "when": "May 15th–17th, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "An Event Apart", + "url": "https://aneventapart.com/event/boston-2017", + "where": "Boston, MA", + "when": "May 15th–17th, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Beyond Tellerrand", + "url": "http://beyondtellerrand.com/", + "where": "Dusseldorf, Germany", + "when": "May 15th–17th, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days - Athens", + "url": "https://voxxeddays.com/athens/", + "where": "Athens, Greece", + "when": "May 18-20, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Syntax", + "url": "https://2017.syntaxcon.com/", + "where": "North Charleston", + "when": "May 18-19, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "React Europe", + "url": "https://www.react-europe.org/", + "where": "Paris, France", + "when": "May 18-19, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "DEVit Conference", + "url": "http://devitconf.org/", + "where": "Thessaloniki, Greece", + "when": "May 20-21, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "GraphQL-Europe", + "url": "https://graphql-europe.org/", + "where": "Berlin, Germany", + "when": "May 21st, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "User Experience Lisbon", + "url": "https://www.ux-lx.com/", + "where": "Lisbon, Portugal", + "when": "May 23-26, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Front-Trends", + "url": "https://2017.front-trends.com/", + "where": "Warsaw, Poland", + "when": "May 24-26, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "UX London", + "url": "http://2017.uxlondon.com/", + "where": "London, UK", + "when": "May 24-26, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Frontend United", + "url": "http://frontendunited.org/", + "where": "Athens, Greece", + "when": "May 26-27, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "NG-Cruise", + "url": "https://ngcruise.com/#/", + "where": "Miami, Florida", + "when": "May 29 - June 2, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "RevolutionConf", + "url": "https://www.revolutionconf.com", + "where": "Virginia Beach, VA", + "when": "June 1-2, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days - Singapore", + "url": "https://voxxeddays.com/singapore/", + "where": "Marina Bay Sands, Singapore", + "when": "June 2, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Kerning", + "url": "http://2017.kerning.it", + "where": "Faenza, Italy", + "when": "June 7-9, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "UX Scotland", + "url": "http://uxscotland.net/2017/", + "where": "Edinburgh, UK", + "when": "June 7-9, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Webconf.asia", + "url": "https://webconf.asia/", + "where": "Hong Kong", + "when": "June 3, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Elm Europe", + "url": "https://elmeurope.org/", + "where": "Paris, France", + "when": "June 8-9, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Generate", + "url": "https://www.generateconf.com/", + "where": "San Francisco, CA", + "when": "June 9, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "ReactJS Day", + "url": "https://www.reactjsday.it", + "where": "Verona, Italy", + "when": "June 10, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "SmashingConf", + "url": "http://lanyrd.com/2017/smashingconf-new-york/", + "where": "New York City, NY", + "when": "June 13-14, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "ConvergeSE", + "url": "http://convergese.com/", + "where": "Columbia, SC", + "when": "June 14-16, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "DinosaurJS", + "url": "http://dinosaurjs.org", + "where": "Denver, CO", + "when": "June 15, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "O'Reilly Fluent", + "url": "http://conferences.oreilly.com/fluent/fl-ca", + "where": "San Jose, CA", + "when": "June 19-22, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "An Event Apart", + "url": "https://aneventapart.com/event/washington-dc-2017", + "where": "Washington, DC", + "when": "July 10-12, 2017", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "Design & Content Conference", + "url": "http://www.designcontentconf.com/", + "where": "Vancouver, BC Canada", + "when": "July 17-19, 2017", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "React Rally", + "url": "http://www.reactrally.com/", + "where": "Salt Lake City, UT", + "when": "August 24-25, 2017", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "BrazilJS", + "url": "https://braziljs.org/conf/", + "where": "Porto Alegre, Brazil", + "when": "August 25-26, 2017", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "An Event Apart", + "url": "https://aneventapart.com/event/chicago-2017", + "where": "Chicago, IL", + "when": "August 28-30, 2017", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "Frontend Conference Zurich", + "url": "https://frontendconf.ch/", + "where": "Zurich, Switzerland", + "when": "August 31 - September 1, 2017", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "Nginx Conf", + "url": "https://www.nginx.com/nginxconf/", + "where": "Portland, Oregon", + "when": "September 6-8, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "React Native EU", + "url": "https://react-native.eu/", + "where": "Wroclaw, Poland", + "when": "September 6-7, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "NordicJS", + "url": "http://nordicjs.com", + "where": "Stockholm, Sweden", + "when": "September 7-8, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Refresh", + "url": "http://refresh.rocks/", + "where": "Tallinn, Estonia", + "when": "September 8, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "FrontTalks", + "url": "http://fronttalks.ru/", + "where": "Ekaterinburg, Russia", + "when": "September 16-17, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "SmashingConf Freiburg 2017", + "url": "https://shop.smashingmagazine.com/products/smashingconf-freiburg-2017", + "where": "Freiburg, Germany", + "when": "September 11th–12th, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Generate", + "url": "https://www.generateconf.com/", + "where": "London, UK", + "when": "September 20–22, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "React Boston", + "url": "http://www.reactboston.com/", + "where": "Boston, MA", + "when": "September 23–24, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Web Unleashed 2017", + "url": "http://fitc.ca/event/webu17/", + "where": "Toronto, Ontario Canada", + "when": "September 25th–26th, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Node.js Interactive North America 2017", + "url": "http://events.linuxfoundation.org/events/node-interactive", + "where": "Vancouver, BC Canada", + "when": "October 4th–6th, 2017", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "GitHub Universe", + "url": "https://githubuniverse.com/", + "where": "San Francisco, California", + "when": "October 10-12, 2017", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "SmashingConf Barcelona 2017", + "url": "https://shop.smashingmagazine.com/products/smashingconf-barcelona-2017", + "where": "Barcelona, Spain", + "when": "October 17th–18th, 2017", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "Empire JS", + "url": "http://2017.empirejs.org/", + "where": "New York City, NY", + "when": "October 22, 2017", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "Full Stack TO", + "url": "http://fsto.co/", + "where": "Toronto, Ontario Canada", + "when": "October 23-24, 2017", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "Revolve Conference", + "url": "https://2017.revolveconference.com/", + "where": "Charleston, SC", + "when": "October 25-27, 2017", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "An Event Apart", + "url": "https://aneventapart.com/event/san-francisco-2017", + "where": "San Francisco, CA", + "when": "October 30- November 1, 2017", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "Beyond Tellerrand", + "url": "http://beyondtellerrand.com/", + "where": "Berlin, Germany", + "when": "November 6–8, 2017", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "Angular Connect", + "url": "http://angularconnect.com/", + "where": "Excel, London", + "when": "November 7-8, 2017", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "An Event Apart", + "url": "https://aneventapart.com/event/denver-2017", + "where": "Denver, CO", + "when": "December 11-13, 2017", + "month": "December", + "submissionDeadline": "" + }, + { + "title": "WordCamp Europe 2017", + "url": "https://2017.europe.wordcamp.org/", + "where": "Paris, France", + "when": "June 15-17, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Web Rebels", + "url": "https://webrebels.org/", + "where": "Oslo, Norway", + "when": "June 01-02, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "WeAreDevelopers", + "url": "http://www.wearedevelopers.org/", + "where": "Vienna, Austria", + "when": "May 11-12, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "CodeFest", + "url": "https://2017.codefest.ru", + "where": "Novosibirsk, Russia", + "when": "April 1-2, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "DUMP", + "url": "http://dump-conf.ru/", + "where": "Ekaterinburg, Russia", + "when": "April 14, 2017", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "JS Day Italy", + "url": "https://2017.jsday.it/", + "where": "Verona, Italy", + "when": "May 10-11, 2017", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Holy JS", + "url": "https://holyjs-piter.ru/", + "where": "Saint-Petersburg, Russia", + "when": "June 2-3, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Frontend Conf", + "url": "http://frontendconf.ru/", + "where": "Moscow, Russia", + "when": "June 5-6, 2017", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "NodeConf Argentina", + "url": "https://2017.nodeconf.com.ar/", + "where": "Buenos Aires, Argentina", + "when": "October 26-28, 2017", + "month": "October", + "submissionDeadline": "July 15, 2017" + }, + { + "title": "Full Stack Fest", + "url": "https://fullstackfest.barcelona", + "where": "Barcelona, Spain", + "when": "September 4-8, 2017", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "JS Remote Conf", + "url": "http://jsremoteconf.com/", + "where": "Online", + "when": "January 14–16, 2016", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "O’Reilly Design Conference", + "url": "http://oreil.ly/1NRw8kd", + "where": "San Francisco, CA, USA", + "when": "January 19–22, 2016", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "SVG Summit 2016", + "url": "http://environmentsforhumans.com/2016/svg-summit/", + "where": "Online", + "when": "January 21, 2016", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "Awwwards Conference – Amsterdam 2016", + "url": "http://conference.awwwards.com/amsterdam-2016/", + "where": "Amsterdam, Netherlands", + "when": "January 27–29, 2016", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "A Day of REST", + "url": "http://feelingrestful.com/", + "where": "London, UK", + "when": "January 28, 2016", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "VOXXED Days Berlin 2016", + "url": "https://voxxeddays.com/berlin16/", + "where": "Berlin, Germany", + "when": "January 28–29, 2016", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "PhoneGap Day US 2016", + "url": "http://pgday.phonegap.com/", + "where": "Lehi, UT, USA", + "when": "January 28–29, 2016", + "month": "January", + "submissionDeadline": "" + }, + { + "title": "Agile Content Conf 2016", + "url": "https://2016.agilecontentconf.com/", + "where": "London, UK", + "when": "February 1–2, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "JFokus 2016", + "url": "http://www.jfokus.se/jfokus/", + "where": "Stockholm, Sweden", + "when": "February 8–10, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "SustainableUX", + "url": "http://sustainableux.com/", + "where": "Online", + "when": "February 9, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "ConveyUX 2016", + "url": "http://conveyux.com/", + "where": "Seattle, WA, USA", + "when": "February 9–11, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Webstock 2016", + "url": "http://www.webstock.org.nz/16/", + "where": "Wellington, New Zealand", + "when": "February 9–12, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "DevNexus 2016", + "url": "http://www.devnexus.com/", + "where": "Atlanta, GA, USA", + "when": "February 15–17, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Open Set Soul Edition 2016", + "url": "http://www.openset.nl/oskorea/opensetdutchdesignseoulsessions_en.html", + "where": "Seoul, South Korea", + "when": "February 15–27, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Mobile Growth Summit 2016", + "url": "http://bit.ly/MobileGrowthSummit16", + "where": "San Francisco, CA, USA", + "when": "February 17–18, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "World IA Day 2016", + "url": "http://www.2016.worldiaday.org/", + "where": "At locations on every continent", + "when": "February 20, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Bulgaria Web Summit 2016", + "url": "http://bulgariawebsummit.com/", + "where": "Sofia, Bulgaria", + "when": "February 20, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "The Rolling Scopes Conference", + "url": "http://2016.conf.rollingscopes.com/", + "where": "Minsk, Belarus", + "when": "February 20–21, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "React.js Conf", + "url": "http://conf.reactjs.com/", + "where": "San Francisco, CA, USA", + "when": "February 22–23, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "UX Riga 2016", + "url": "http://www.uxriga.lv/", + "where": "Riga, Latvia", + "when": "February 25, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Typoday 2016", + "url": "http://www.typoday.in/", + "where": "Bangalore, India", + "when": "February 25–27, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Planning for a Higher Ed Website Redesign", + "url": "http://www.academicimpressions.com/conference/planning-higher-ed-website-redesign", + "where": "Cincinnati, OH, USA", + "when": "February 29–March 2, 2016", + "month": "February", + "submissionDeadline": "" + }, + { + "title": "Interaction16", + "url": "http://interaction16.ixda.org/", + "where": "Helsinki, Finland", + "when": "March 1–4, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Riga Dev Day 2016", + "url": "http://www.rigadevday.lv/", + "where": "Riga, Latvia", + "when": "March 2–4, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Voxxed Days Zurich", + "url": "https://voxxeddays.com/zurich16/", + "where": "Zurich, Switzerland", + "when": "March 3, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "MIDWEST PHP 2016", + "url": "http://2016.midwestphp.org/", + "where": "Minneapolis, MN, USA", + "when": "March 4–5, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "QCon London 2016", + "url": "http://qconlondon.com/", + "where": "London, UK", + "when": "March 7–11, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "O’Reilly Fluent Conference 2016", + "url": "http://conferences.oreilly.com/fluent/javascript-html-us", + "where": "San Francisco, CA", + "when": "March 8–10, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "UXHK 2016", + "url": "http://www.uxhongkong.com/", + "where": "Hong Kong, China", + "when": "March 11–12, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "SXSW 2016", + "url": "http://www.sxsw.com/", + "where": "Austin, TX, USA", + "when": "March 11–20, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Dev Bootcamp San Francisco March Info Session", + "url": "https://www.eventbrite.com/…san-francisco-march-info-session-tickets-22132155874?ref=ebapi", + "where": "United States,San Francisco", + "when": "March 14th, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "MobileTech Conference Spring 2016", + "url": "https://mobiletechcon.de/", + "where": "Munich, Germany", + "when": "March 14–17, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Ruby for Beginners", + "url": "https://www.eventbrite.com/e/ruby-for-beginners-tickets-21643963678?ref=ebapi", + "where": "United States,San Francisco", + "when": "March 15th, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "SmashingConf Oxford 2016", + "url": "http://www.smashingconf.com/", + "where": "Oxford, UK", + "when": "March 15–16, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "DIBI 2016", + "url": "http://dibiconference.com/", + "where": "Edinburgh, UK", + "when": "March 17–18, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Codemotion Roma 2016", + "url": "http://rome2016.codemotionworld.com/", + "where": "ItalyItaly,Rome", + "when": "March 18th–19th, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Phase 0 Virtual Information Session", + "url": "https://www.eventbrite.com/e/phase-0-virtual-information-session-tickets-22308315773", + "where": "Online conference", + "when": "March 19th, 2016", + "month": "March" + }, + { + "title": "Ruby Remote Conf", + "url": "https://allremoteconfs.com/ruby-2016", + "where": "Online", + "when": "March 23-25, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "EmberConf 2016", + "url": "http://emberconf.com/", + "where": "Portland, OR, USA", + "when": "March 28–30, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "QCon SP 2016", + "url": "http://qconsp.com/", + "where": "São Paolo, Brazil", + "when": "March 28–April 1, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "UX in the City: Oxford 2016", + "url": "http://uxinthecity.net/2016/oxford/", + "where": "Oxford, UK", + "when": "March 31–April 1, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Workshop: Building JavaScript Applications with ReactJS", + "url": "http://www.whiteoctoberevents.co.uk/event/reactjs-workshop-march-16/", + "where": "England,London", + "when": "March 21st, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Dev Bootcamp March Virtual Info Session", + "url": "https://www.eventbrite.com/e/dev-bootcamp-march-virtual-info-session-tickets-22477541933", + "where": "Online conference", + "when": "March 24th, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Meet the TAG - London", + "url": "", + "where": "England,London", + "when": "March 29th, 2016, 6pm", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "RWD Summit 2016", + "url": "http://environmentsforhumans.com/2016/responsive-web-design-summit/", + "where": "Online conference", + "when": "March 29th–31st, 2016", + "month": "March", + "submissionDeadline": "" + }, + { + "title": "Fronteers Spring Thing 2016", + "url": "https://fronteers.nl/spring", + "where": "Amsterdam, Netherlands", + "when": "April 1, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Space City JS 2016", + "url": "http://spacecity.codes/", + "where": "Houston, TX, USA", + "when": "April 2, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "MobCon Europe 2016", + "url": "http://mobcon.com/mobcon-europe/", + "where": "Sofia, Bulgaria", + "when": "April 4, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "An Event Apart 2016: Seattle", + "url": "http://aneventapart.com/event/seattle-2016", + "where": "Seattle, WA, USA", + "when": "April 4–6, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "SmashingConf San Francisco 2016", + "url": "http://smashingconf.com/sf-2016/", + "where": "San Francisco, CA, USA", + "when": "April 5–6, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Le Web a Quebec 2016 — 6e edition", + "url": "http://www.webaquebec.org/", + "where": "Quebec, Canada", + "when": "April 6–8, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "WebVisions New York City 2016", + "url": "http://www.webvisionsevent.com/", + "where": "New York City, NY, USA", + "when": "April 7, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Respond 2016", + "url": "http://www.webdirections.org/respond16/", + "where": "Melbourne, Australia", + "when": "April 11-12, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Lone Star PHP 2016", + "url": "http://lonestarphp.com/", + "where": "Dallas, TX, USA", + "when": "April 7–9, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Jazoon Techdays Spring 2016", + "url": "http://jazoon.com/", + "where": "Berne, Switzerland", + "when": "April 8, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Yggdrasil 2016", + "url": "http://yggdrasilkonferansen.no/", + "where": "Sandefjord, Norway", + "when": "April 11–12, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Now What? Conference 2016", + "url": "http://www.nowwhatconference.com/", + "where": "Sioux Falls, SD, USA", + "when": "April 13–14, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Peers Conference 2016", + "url": "http://peersconf.com/", + "where": "St. Petersburg, FL, USA", + "when": "April 13–15, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "ConvergeSE 2016", + "url": "http://convergese.com/", + "where": "Columbia, SC, USA", + "when": "April 13–15, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "ACE! 2016", + "url": "http://aceconf.com/", + "where": "Krakow, Poland", + "when": "April 14–15, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "From Business to Buttons", + "url": "http://frombusinesstobuttons.com/", + "where": "Stockholm, Sweden", + "when": "April 14–16, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "JSConf 2016", + "url": "https://jsconf.uy/", + "where": "Montevideo, Uruguay", + "when": "April 15–16, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "JavaScript Frameworks Day 2016", + "url": "http://frameworksdays.com/event/js-frameworks-day-2016", + "where": "Kiev, Ukraine", + "when": "April 16, 2016", + "month": "April" + }, + { + "title": "Ancient City Ruby 2016", + "url": "", + "where": "United States,St. Augustine", + "when": "April 6th–8th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Lone Star PHP 2016", + "url": "", + "where": "United States,Addison", + "when": "April 7th–9th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Building a Real-time App with React and Firebase", + "url": "", + "where": "Online conference", + "when": "April 7th, 2016, 9am", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "CoderDojo Nürnberg #1", + "url": "", + "where": "Germany,Nuremberg", + "when": "April 10th 2016, 4pm", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Nürnberg Web Week 2016", + "url": "", + "where": "Germany,Nuremberg", + "when": "April 11th–18th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Accessibility Club #3", + "url": "http://accessibility-club.org/", + "where": "Germany,Nürnberg", + "when": "April 12th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Digital Croydon #8 Laura Elizabeth, Jason Bootle, Amy Whitney", + "url": "", + "where": "England,London Borough of Croydon", + "when": "April 14th, 2016, 6:30pm", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Using External Plugins with React", + "url": "http://www.oreilly.com/online-training/react-for-web-developers.html", + "where": "Online conference", + "when": "April 14th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Hexagon Geospatial IGNITE Session - Atlanta", + "url": "https://www.eventbrite.com/e/hexagon-geospatial-ignite-session-atlanta-tickets-22668441920?ref=ebapi", + "where": "United States,Norcross", + "when": "April 15th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "2016 DC-Baltimore Perl Workshop", + "url": "http://dcbpw.org/dcbpw2016/", + "where": "United States,Balitmore", + "when": "April 16th–17th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Workshop: Rapid API development with Node.js and LoopBack", + "url": "", + "where": "England,Oxford", + "when": "April 20th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Industry Conf", + "url": "https://industryconf.com/", + "where": "Newcastle upon Tyne, England", + "when": "April 20, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "MCE3", + "url": "http://mceconf.com/", + "where": "Warsaw, Poland", + "when": "April 21–22, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Render Conf", + "url": "http://www.render-conf.com/", + "where": "Oxford, England", + "when": "April 21-22, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Generate NYC 2016", + "url": "http://www.generateconf.com/new-york-2016", + "where": "New York City, NY, USA", + "when": "April 22, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "RSJS", + "url": "http://rsjs.org/2016/", + "where": "Porto Alegre, Brazil", + "when": "April 23, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "JSUnconf", + "url": "http://2016.jsunconf.eu/", + "where": "Hamburg, Germany", + "when": "April 23-24, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "JET Conference", + "url": "", + "where": "Belarus,Minsk", + "when": "April 25th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Future of Web Design London", + "url": "https://futureofwebdesign.com/london-2016/", + "where": "London, England", + "when": "April 25-27, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Open Vis Conf", + "url": "https://openvisconf.com/", + "where": "Boston, MA, USA", + "when": "April 25–26, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "M2M WORLD CONGRESS 2016", + "url": "http://www.m2mconference.com/", + "where": "London, UK", + "when": "April 26–27, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Squares Conference", + "url": "http://squaresconference.com/", + "where": "Dallas (Grapevine), Texas USA", + "when": "April 27-29, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Workshop: Angular 2", + "url": "", + "where": "England,London", + "when": "April 29th, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "JSDayES 2016", + "url": "http://jsday.es/", + "where": "Madrid, Spain", + "when": "April 29-30, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Front in Rio – HTML Edition", + "url": "http://frontinrio.com.br/", + "where": "Rio de Janeiro, Brazil", + "when": "April 30, 2016", + "month": "April", + "submissionDeadline": "" + }, + { + "title": "Future Insights Live", + "url": "https://futureinsightslive.com/", + "where": "Chicago, Illinois USA", + "when": "May 2-5, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "ng-conf", + "url": "http://www.ng-conf.org/", + "where": "Salt Lake City, Utah USA", + "when": "May 4-6, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "IA Summit 2016", + "url": "http://2016.iasummit.org/", + "where": "Atlanta, GA, USA", + "when": "May 4–8, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Syntax Convention", + "url": "http://syntaxcon.com/", + "where": "Charleston, South Carolina USA", + "when": "May 6-7, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Beyond Tellerrand", + "url": "http://beyondtellerrand.com/", + "where": "Düsseldorf, Germany", + "when": "May 9-11, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "DRUPALCON", + "url": "https://events.drupal.org/neworleans2016", + "where": "New Orleans, LA, USA", + "when": "May 9–13, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "CSSConf Budapest", + "url": "http://cssconfbp.rocks/", + "where": "Budapest, Hungary", + "when": "May 11, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "jsday", + "url": "http://2016.jsday.it/", + "where": "Verona, Italy", + "when": "May 11-12, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "UX Alive", + "url": "http://www.uxalive.com/", + "where": "Istanbul, Turkey", + "when": "May 11-13, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "JsDay", + "url": "http://2016.jsday.it/", + "where": "Verona, Italy", + "when": "May 11–12, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "UX Alive", + "url": "http://www.uxalive.com/", + "where": "Istanbul, Turkey", + "when": "May 11–13, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "JSConf Budapest", + "url": "http://jsconfbp.com/", + "where": "Budapest, Hungary", + "when": "May 12-13, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Create Upstate", + "url": "http://createupstate.com/", + "where": "Syracuse, NY, USA", + "when": "May 12–13, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Front", + "url": "http://www.frontutah.com/", + "where": "Salt Lake City, UT, USA", + "when": "May 12–13, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "JSConf Budapest", + "url": "http://jsconfbp.com/", + "where": "Budapest, Hungary", + "when": "May 12–13, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "phpDay", + "url": "http://2016.phpday.it/", + "where": "Verona, Italy", + "when": "May 13–14, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "An Event Apart Boston", + "url": "http://aneventapart.com/event/boston-2016", + "where": "Boston, Massachusetts USA", + "when": "May 16-18, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "SDD Conf", + "url": "http://www.sddconf.com/", + "where": "London, UK", + "when": "May 16–20, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "OSCON", + "url": "http://conferences.oreilly.com/oscon/open-source-us", + "where": "Austin, TX, USA", + "when": "May 18–19, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "UX London", + "url": "http://2016.uxlondon.com/", + "where": "London, UK", + "when": "May 18–20, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Front-Trends", + "url": "http://front-trends.com/", + "where": "Warsaw, Poland", + "when": "May 18–20, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "PhoneGap Day EU 2016", + "url": "http://pgday.phonegap.com/eu2016/", + "where": "Amsterdam, Netherlands", + "when": "May 19-20, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Port 80", + "url": "http://port80events.co.uk/", + "where": "Newport, UK", + "when": "May 20, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "JandBeyong - An international Joomla! Conference", + "url": "http://JandBeyong.org/", + "where": "Barcelona, Spain", + "when": "May 20-22, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "GOTO Chicago", + "url": "http://gotocon.com/chicago-2016", + "where": "Chicago, IL, USA", + "when": "May 23–26, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "User Experience Lisbon", + "url": "https://www.ux-lx.com/", + "where": "Lisbon, Portugal", + "when": "May 24-27, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "DevSum16", + "url": "http://www.devsum.se/", + "where": "Stockholm, Sweden", + "when": "May 25-27, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Empire JS", + "url": "http://empirejs.org/", + "where": "New York City, New York USA", + "when": "May 26-27, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Frontend United", + "url": "http://frontendunited.org/", + "where": "Ghent, Belgium", + "when": "May 27-28, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "UpFront 2016", + "url": "http://upfrontconf.com/", + "where": "Manchester, UK", + "when": "May 27, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Sud Web 2016", + "url": "http://sudweb.fr/", + "where": "Bordeaux, France", + "when": "May 27–28, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "Webinale 2016", + "url": "https://webinale.de/", + "where": "Berlin, Germany", + "when": "May 29–June 02, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "UXPA 2016", + "url": "http://www.uxpa2016.org/", + "where": "Seattle, WA, USA", + "when": "May 31–June 03, 2016", + "month": "May", + "submissionDeadline": "" + }, + { + "title": "CSSconf Nordic 2016", + "url": "http://cssconf.no/", + "where": "Oslo, Norway", + "when": "June 1, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "ScotlandCSS 2016", + "url": "http://scotlandcss.launchrock.com/", + "where": "Edinburgh, UK", + "when": "June 1, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Web Rebels 2016", + "url": "https://www.webrebels.org/", + "where": "Oslo, Norway", + "when": "June 2–3, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Soap! 2016", + "url": "http://soapconf.com/", + "where": "Krakow, Poland", + "when": "June 2–3, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "UXScotland 2016", + "url": "http://uxscotland.net/2016/", + "where": "Edinburgh, UK", + "when": "June 8–10, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "M-Enabling Summit 2016", + "url": "http://www.m-enabling.com/", + "where": "Washington, D.C., USA", + "when": "June 13–14, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "QCon New York 2016", + "url": "https://qconnewyork.com/", + "where": "New York City, NY, USA", + "when": "June 13–17, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Front End Design Conference", + "url": "http://frontenddesignconference.com/", + "where": "St. Petersburg, Florida USA", + "when": "June 15-17, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Droidcon Berlin 2016", + "url": "http://droidcon.de/", + "where": "Berlin, Germany", + "when": "June 15–17, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "DWX Developer Week 2016", + "url": "http://www.developer-week.de/", + "where": "Nuremberg, Germany", + "when": "June 20–23, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "KCDC", + "url": "http://www.kcdc.info/#!/", + "where": "Kansas City, Utah", + "when": "June 22-24, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Dinosaur.js", + "url": "http://dinosaurjs.org/", + "where": "Denver, Colorado USA", + "when": "June 24, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Web Design Day", + "url": "http://webdesignday.com", + "where": "Pittsburgh, Pennsylvania USA", + "when": "June 24, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Devoxx Poland 2016", + "url": "http://devoxx.pl/", + "where": "Krakow, Poland", + "when": "June 22–25, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "The Lead Developer 2016", + "url": "http://2016.theleaddeveloper.com/", + "where": "London, UK", + "when": "June 23–24, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Dutch PHP Conference 2016", + "url": "http://www.phpconference.nl/", + "where": "Amsterdam, Netherlands", + "when": "June 23–25, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Wicked Good Ember Conf", + "url": "https://wickedgoodember.com", + "where": "Boston, MA, USA", + "when": "June 27-28, 2016", + "month": "June", + "submissionDeadline": "May 13, 2016" + }, + { + "title": "CSSconf Nordic", + "url": "http://cssconf.no/", + "where": "Oslo, Norway", + "when": "June 1, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "Web Rebels", + "url": "https://www.webrebels.org/", + "where": "Oslo, Norway", + "when": "June 2-3, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "ReactEurope 2016", + "url": "https://www.react-europe.org/", + "where": "Paris, France", + "when": "June 2-3, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "ScotlandJS 2016", + "url": "http://scotlandjs.com/", + "where": "Edinburgh, Scotland", + "when": "June 2-3, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "JSCamp Romania", + "url": "http://www.jscamp.ro/", + "where": "Bucharest, Romania", + "when": "June 7, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "UX Scotland", + "url": "http://uxscotland.net/2016/", + "where": "Edinburgh, Scotland", + "when": "June 8, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "enterJS 2016", + "url": "https://www.enterjs.de/", + "where": "Darmstadt, Germany", + "when": "June 14-16, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "CSS Day 2016", + "url": "http://cssday.nl/", + "where": "Amsterdam, The Netherlands", + "when": "June 16-17, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "YGLF", + "url": "http://yougottalovefrontend.com/#page-home", + "where": "Tel Aviv, Israel", + "when": "June 27-28, 2016", + "month": "June", + "submissionDeadline": "" + }, + { + "title": "FrontinSampa", + "url": "http://frontinsampa.com.br/", + "where": "São Paulo, Brazil", + "when": "July 02, 2016", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "FullStack 2016", + "url": "https://skillsmatter.com/conferences/7278-fullstack", + "where": "England, London", + "when": "July 13–15, 2016", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "Generate San Francisco 2016", + "url": "http://generateconf.com/", + "where": "San Francisco, California USA", + "when": "July 15, 2016", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "An Event Apart Washington D.C.", + "url": "http://aneventapart.com/event/washington-dc-2016", + "where": "Washington, D.C. USA", + "when": "July 25-27, 2016", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "ForwardJS 5", + "url": "http://forwardjs.com/", + "where": "San Francisco, California USA", + "when": "July 25-31, 2016", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "CSS Summit 2016", + "url": "http://environmentsforhumans.com/2016/css-summit/", + "where": "online", + "when": "July 26-28, 2016", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "Mobile & Web CodeCamp", + "url": "http://www.mobilewebcodecamp.com/", + "where": "Salt Lake City, Utah USA", + "when": "July 28-29, 2016", + "month": "July", + "submissionDeadline": "" + }, + { + "title": "NDC Sydney", + "url": "http://ndcsydney.com/", + "where": "Sydney, Australia", + "when": "August 1-5, 2016", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "CSSConf Argentina", + "url": "http://cssconfar.com", + "where": "Buenos Aires, Argentina", + "when": "August 7, 2016", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "JSConf Iceland", + "url": "http://2016.jsconf.is/", + "where": "Reykjavik, Iceland", + "when": "August 25-26, 2016", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "React Rally", + "url": "http://www.reactrally.com/", + "where": "Salt Lake City, Utah", + "when": "August 25-26, 2016", + "month": "August", + "submissionDeadline": "April 28, 2016" + }, + { + "title": "BrazilJS Conf", + "url": "https://braziljs.org/conf", + "where": "Porto Alegre, Brazil", + "when": "August 26-27, 2016", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "AltConf", + "url": "http://www.alterconf.com/sessions/cape-town-south-africa", + "where": "Cape Town, South Africa", + "when": "August 27, 2016", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "An Event Apart Chicago", + "url": "http://aneventapart.com/event/chicago-2016", + "where": "Chicago, Illinois USA", + "when": "August 29-31, 2016", + "month": "August", + "submissionDeadline": "" + }, + { + "title": "ColdFront 2016", + "url": "https://2016.coldfrontconf.com/", + "where": "Copenhagen, Denmark", + "when": "September 1, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Frontend Conference Zurich", + "url": "https://frontendconf.ch/", + "where": "Zurich, Switzerland", + "when": "September 1-2, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Generate Sydney 2016", + "url": "http://generateconf.com/", + "where": "Sydney Australia", + "when": "September 5, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Full Stack Fest", + "url": "http://2016.fullstackfest.com/", + "where": "Barcelona, Spain", + "when": "September 5-9, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "SmashingConf Freiburg 2016", + "url": "http://smashingconf.com/", + "where": "Freiburg, Germany", + "when": "September 12-13, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "From the Front", + "url": "http://2016.fromthefront.it/", + "where": "Bologna, Italy", + "when": "September 15-16, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Strange Loop", + "url": "http://www.thestrangeloop.com/", + "where": "St. Louis, USA", + "when": "September 15-17, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "SmartWeb Conference 2016", + "url": "http://www.smartwebconf.com/", + "where": "Bucharest, Romania", + "when": "September 20, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "Generate London 2016", + "url": "http://generateconf.com/", + "where": "London England", + "when": "September 21-23, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "AngularConnect 2016", + "url": "http://angularconnect.com/", + "where": "London, England", + "when": "September 27-28, 2016", + "month": "September", + "submissionDeadline": "" + }, + { + "title": "An Event Apart Orlando", + "url": "http://aneventapart.com/event/orlando-2016", + "where": "Orlando, Florida USA", + "when": "October 3-5, 2016", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "LoopConf", + "url": "https://loopconf.com/", + "where": "Fort Lauderdale, Florida USA", + "when": "October 5-7, 2016", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "Full Stack Toronto", + "url": "https://fsto.co/", + "where": "Toronto, Ontario Canada", + "when": "October 17-18, 2016", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "CSS Dev Conf", + "url": "http://2016.cssdevconf.com/", + "where": "San Antonio, Texas USA", + "when": "October 17-19, 2016", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "Connect.Tech", + "url": "http://connect-js.com/", + "where": "Atlanta, Georgia USA", + "when": "October 20-22, 2016", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "ng-europe 2016", + "url": "https://ngeurope.org/", + "where": "Paris, France", + "when": "October 25-26, 2016", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "An Event Apart San Francisco", + "url": "http://aneventapart.com/event/san-francisco-2016", + "where": "San Francisco, California USA", + "when": "October 31-November 2, 2016", + "month": "October", + "submissionDeadline": "" + }, + { + "title": "Thunder Plains", + "url": "http://thunderplainsconf.com/", + "where": "Oklahoma City, USA", + "when": "November 3-4, 2016", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "SenchaCon 2016", + "url": "http://www.senchacon.com/", + "where": "Las Vegas, Nevada USA", + "when": "November 7-9, 2016", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "Beyond Tellerrand", + "url": "http://beyondtellerrand.com/", + "where": "Berlin Germany", + "when": "November 7-9, 2016", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "FrontierConf London 2016", + "url": "https://www.frontierconf.com/", + "where": "London, England", + "when": "November 16, 2016", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "Generate Bangalore 2016", + "url": "http://www.generateconf.com/", + "where": "Bangalore, India", + "when": "November 25, 2016", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "JS Kongress Munich", + "url": "http://js-kongress.de/", + "where": "Munich, Germany", + "when": "November 28-29, 2016", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "CSSConf Australia", + "url": "http://2016.cssconf.com.au/", + "where": "Melbourne Australia", + "when": "November 30, 2016", + "month": "November", + "submissionDeadline": "" + }, + { + "title": "Decompress", + "url": "http://decompress.com.au/", + "where": "Melbourne, Australia", + "when": "December 2, 2016", + "month": "December", + "submissionDeadline": "" + }, + { + "title": "dotCSS", + "url": "http://www.dotcss.io/", + "where": "Paris, France", + "when": "December 2, 2016", + "month": "December", + "submissionDeadline": "" + }, + { + "title": "dotJS", + "url": "http://www.dotjs.io/", + "where": "Paris, France", + "when": "December 5, 2016", + "month": "December", + "submissionDeadline": "" + } +] \ No newline at end of file diff --git a/admin-panel/src/mocks/index.js b/admin-panel/src/mocks/index.js new file mode 100644 index 0000000..d1a851f --- /dev/null +++ b/admin-panel/src/mocks/index.js @@ -0,0 +1,9 @@ +import conferences from './conferences' +import firebase from 'firebase' + +export function saveEventsToFB() { + const eventsRef = firebase.database().ref('/events') + conferences.forEach(conference => eventsRef.push(conference)) +} + +window.saveEventsToFB = saveEventsToFB \ No newline at end of file diff --git a/admin-panel/src/redux/index.js b/admin-panel/src/redux/index.js index 1a37261..6c5314d 100644 --- a/admin-panel/src/redux/index.js +++ b/admin-panel/src/redux/index.js @@ -4,8 +4,9 @@ import {routerMiddleware} from 'react-router-redux' import thunk from 'redux-thunk' import reducer from './reducer' import history from '../history' +import {randomId} from './middlewares' -const store = createStore(reducer, applyMiddleware(thunk, routerMiddleware(history), logger)) +const store = createStore(reducer, applyMiddleware(thunk, routerMiddleware(history), randomId, logger)) //dev only window.store = store diff --git a/admin-panel/src/redux/middlewares.js b/admin-panel/src/redux/middlewares.js new file mode 100644 index 0000000..05ccb06 --- /dev/null +++ b/admin-panel/src/redux/middlewares.js @@ -0,0 +1,14 @@ +import {getRandomId} from '../helpers'; + +export let randomId = store => next => action => { + let {payload = {}} = action; + let {generateId, ...rest} = payload; + if (!generateId) return next(action); + next({ + ...action, + payload : { + ...rest, + randomId : getRandomId() + } + }) +} \ No newline at end of file diff --git a/admin-panel/src/redux/reducer.js b/admin-panel/src/redux/reducer.js index 34143fe..ddc9f75 100644 --- a/admin-panel/src/redux/reducer.js +++ b/admin-panel/src/redux/reducer.js @@ -2,8 +2,10 @@ import {combineReducers} from 'redux' import {routerReducer as router} from 'react-router-redux' import {reducer as form} from 'redux-form' import authReducer, {moduleName as authModule} from '../ducks/auth' +import usersReducer, {moduleName as userModule} from '../ducks/users' export default combineReducers({ router, form, - [authModule]: authReducer + [authModule] : authReducer, + [userModule] : usersReducer, }) \ No newline at end of file diff --git a/admin-panel/src/redux/saga.js b/admin-panel/src/redux/saga.js new file mode 100644 index 0000000..d9bb32f --- /dev/null +++ b/admin-panel/src/redux/saga.js @@ -0,0 +1,12 @@ +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 * rootSaga() { + yield all([ + peopleSaga(), + authSaga(), + eventsSaga() + ]) +} \ No newline at end of file diff --git a/admin-panel/src/setupTests.js b/admin-panel/src/setupTests.js new file mode 100644 index 0000000..88c8c3b --- /dev/null +++ b/admin-panel/src/setupTests.js @@ -0,0 +1,4 @@ +import Enzyme from 'enzyme' +import Adapter from 'enzyme-adapter-react-16' + +Enzyme.configure({ adapter: new Adapter() }) diff --git a/admin-panel/yarn.lock b/admin-panel/yarn.lock index c9c597c..2efe11e 100644 --- a/admin-panel/yarn.lock +++ b/admin-panel/yarn.lock @@ -50,6 +50,10 @@ version "0.2.4" resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.4.tgz#ec8438b780baff09254076efe345f30fdb41243e" +"@types/node@*": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.1.tgz#4ec3020bcdfe2abffeef9ba3fbf26fca097514b5" + abab@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -963,7 +967,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1352,6 +1356,17 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + chokidar@^1.6.0, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -1388,6 +1403,10 @@ clap@^1.0.9: dependencies: chalk "^1.1.3" +classnames@^2.2.3: + version "2.2.5" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" + clean-css@4.1.x: version "4.1.9" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301" @@ -1474,6 +1493,10 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" +colors@0.5.x: + version "0.5.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" + colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1702,7 +1725,7 @@ css-loader@0.28.7: postcss-value-parser "^3.3.0" source-list-map "^2.0.0" -css-select@^1.1.0: +css-select@^1.1.0, css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" dependencies: @@ -1935,6 +1958,10 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +discontinuous-range@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -1971,7 +1998,11 @@ dom-converter@~0.1: dependencies: utila "~0.3" -dom-serializer@0: +"dom-helpers@^2.4.0 || ^3.0.0": + version "3.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" + +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: @@ -1992,7 +2023,7 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" -domelementtype@1: +domelementtype@1, domelementtype@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" @@ -2006,6 +2037,12 @@ domhandler@2.1: dependencies: domelementtype "1" +domhandler@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + dependencies: + domelementtype "1" + domutils@1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" @@ -2019,6 +2056,13 @@ domutils@1.5.1: dom-serializer "0" domelementtype "1" +domutils@^1.5.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" + dependencies: + dom-serializer "0" + domelementtype "1" + dot-prop@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" @@ -2096,10 +2140,45 @@ enhanced-resolve@^3.4.0: object-assign "^4.0.1" tapable "^0.2.7" -entities@~1.1.1: +entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" +enzyme-adapter-react-16@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.0.tgz#86c5db7c10f0be6ec25d54ca41b59f2abb397cf4" + dependencies: + enzyme-adapter-utils "^1.1.0" + lodash "^4.17.4" + object.assign "^4.0.4" + object.values "^1.0.4" + prop-types "^15.5.10" + react-test-renderer "^16.0.0-0" + +enzyme-adapter-utils@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.2.0.tgz#7f4471ee0a70b91169ec8860d2bf0a6b551664b2" + dependencies: + lodash "^4.17.4" + object.assign "^4.0.4" + prop-types "^15.5.10" + +enzyme@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.2.0.tgz#998bdcda0fc71b8764a0017f7cc692c943f54a7a" + dependencies: + cheerio "^1.0.0-rc.2" + function.prototype.name "^1.0.3" + has "^1.0.1" + is-subset "^0.1.1" + lodash "^4.17.4" + object-is "^1.0.1" + object.assign "^4.0.4" + object.entries "^1.0.4" + object.values "^1.0.4" + raf "^3.4.0" + rst-selector-parser "^2.2.3" + errno@^0.1.3, errno@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" @@ -2112,7 +2191,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.7.0: +es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.10.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" dependencies: @@ -2779,10 +2858,18 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2, function-bind@^1.1.1: +function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" +function.prototype.name@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.3.tgz#0099ae5572e9dd6f03c97d023fd92bcc5e639eac" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + is-callable "^1.1.3" + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -3125,6 +3212,17 @@ html-webpack-plugin@2.29.0: pretty-error "^2.0.2" toposort "^1.0.0" +htmlparser2@^3.9.1: + version "3.9.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^2.0.2" + htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -3476,6 +3574,10 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + is-svg@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" @@ -4055,6 +4157,10 @@ lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -4096,7 +4202,7 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: @@ -4321,6 +4427,14 @@ ncname@1.0.x: dependencies: xml-char-classes "^1.0.0" +nearley@^2.7.10: + version "2.11.0" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.11.0.tgz#5e626c79a6cd2f6ab9e7e5d5805e7668967757ae" + dependencies: + nomnom "~1.6.2" + railroad-diagrams "^1.0.0" + randexp "^0.4.2" + negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" @@ -4403,6 +4517,13 @@ node-status-codes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" +nomnom@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.6.2.tgz#84a66a260174408fc5b77a18f888eccc44fb6971" + dependencies: + colors "0.5.x" + underscore "~1.4.4" + nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -4483,10 +4604,31 @@ object-hash@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b" -object-keys@^1.0.8: +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + +object-keys@^1.0.10, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object.assign@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + object-keys "^1.0.10" + +object.entries@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -4494,6 +4636,15 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" +object.values@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + obuf@^1.0.0, obuf@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e" @@ -4657,6 +4808,12 @@ parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + dependencies: + "@types/node" "*" + parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -5206,12 +5363,23 @@ querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" -raf@3.4.0: +raf@3.4.0, raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" dependencies: performance-now "^2.1.0" +railroad-diagrams@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + +randexp@^0.4.2: + version "0.4.6" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + dependencies: + discontinuous-range "1.0.0" + ret "~0.1.10" + randomatic@^1.1.3: version "1.1.7" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" @@ -5375,6 +5543,24 @@ react-scripts@1.0.17: optionalDependencies: fsevents "1.1.2" +react-test-renderer@^16.0.0-0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211" + dependencies: + fbjs "^0.8.16" + object-assign "^4.1.1" + prop-types "^15.6.0" + +react-virtualized@^9.13.0: + version "9.13.0" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.13.0.tgz#83e4d984271a37631225e5fe6faeaeada6e59f53" + dependencies: + babel-runtime "^6.23.0" + classnames "^2.2.3" + dom-helpers "^2.4.0 || ^3.0.0" + loose-envify "^1.3.0" + prop-types "^15.5.4" + react@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" @@ -5497,6 +5683,10 @@ redux-logger@^3.0.6: dependencies: deep-diff "^0.3.5" +redux-saga@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.16.0.tgz#0a231db0a1489301dd980f6f2f88d8ced418f724" + redux-thunk@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" @@ -5726,6 +5916,10 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -5745,6 +5939,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" +rst-selector-parser@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" + dependencies: + lodash.flattendeep "^4.4.0" + nearley "^2.7.10" + run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -6393,6 +6594,10 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" +underscore@~1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" + uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"