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/README.md b/admin-panel/README.md index f445bfc..6c0fee0 100644 --- a/admin-panel/README.md +++ b/admin-panel/README.md @@ -1,3 +1,8 @@ ##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 Загружать и отображать список ивентов \ No newline at end of file diff --git a/admin-panel/package.json b/admin-panel/package.json index 5fbeba9..62d5d33 100644 --- a/admin-panel/package.json +++ b/admin-panel/package.json @@ -8,6 +8,7 @@ "history": "^4.7.2", "immutable": "^3.8.2", "logger": "^0.0.1", + "prop-types": "^15.6.0", "react": "^16.2.0", "react-dom": "^16.2.0", "react-redux": "^5.0.6", @@ -17,6 +18,7 @@ "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" }, diff --git a/admin-panel/src/App.js b/admin-panel/src/App.js deleted file mode 100644 index fd110f7..0000000 --- a/admin-panel/src/App.js +++ /dev/null @@ -1,23 +0,0 @@ -import React, { Component } from 'react' -import {Route} from 'react-router-dom' -import Auth from './components/routes/auth' -import Admin from './components/routes/Admin' -import ProtectedRoute from './components/common/ProtectedRoute' - -class App extends Component { - static propTypes = { - - }; - - render() { - return ( -
-

Hello world

- - -
- ) - } -} - -export default App \ No newline at end of file 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..62fffd3 --- /dev/null +++ b/admin-panel/src/components/App.js @@ -0,0 +1,32 @@ +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 EventPage from './routes/EventPage' + +class App extends Component { + static propTypes = { + + }; + + render() { + return ( +
+

Hello world

+ + + + + +
+ ) + } +} + +export default App \ 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/EventList.js b/admin-panel/src/components/events/EventList.js new file mode 100644 index 0000000..42aa12b --- /dev/null +++ b/admin-panel/src/components/events/EventList.js @@ -0,0 +1,42 @@ +import React, {Component} from 'react' + +class EventList extends Component { + static propTypes = { + + }; + + render() { + const {events} = this.props + + return ( + + + + + + + + + + + + + { + events.map(event => + + + + + + + + + ) + } + +
MonthDeadlineTitleURLWhenWhere
{event.month}{event.submissionDeadline}{event.title}{event.url}{event.when}{event.where}
+ ) + } +} + +export default EventList \ 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..4b2101b --- /dev/null +++ b/admin-panel/src/components/people/NewPersonForm.js @@ -0,0 +1,48 @@ +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 = { + + }; + + componentWillReceiveProps = (nextProps) => { + const {submitSucceeded, reset} = this.props; + + if (nextProps.submitSucceeded === true && nextProps.submitSucceeded !== submitSucceeded) { + reset('person'); + } + } + + 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/PersonItem.js b/admin-panel/src/components/people/PersonItem.js new file mode 100644 index 0000000..7bff7ce --- /dev/null +++ b/admin-panel/src/components/people/PersonItem.js @@ -0,0 +1,22 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' + +class PersonItem extends Component { + static propTypes = { + person: PropTypes.object.isRequired + }; + + render() { + const {person} = this.props + + return ( +
+
Email: {person.email}
+
First name: {person.firstName}
+
Last name: {person.lastName}
+
+ ) + } +} + +export default PersonItem \ No newline at end of file diff --git a/admin-panel/src/components/people/PersonList.js b/admin-panel/src/components/people/PersonList.js new file mode 100644 index 0000000..fa1c91a --- /dev/null +++ b/admin-panel/src/components/people/PersonList.js @@ -0,0 +1,37 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' + +import PersonItem from './PersonItem' + +class PersonList extends Component { + static propTypes = { + persons: PropTypes.object + }; + + getPersonList = (persons) => { + return ( +
    + { + persons.map(person => +
  • + +
  • + ) + } +
+ ) + } + + render() { + const {persons} = this.props + + return ( +
+

People list

+ {persons.isEmpty() ?

List is empty

: this.getPersonList(persons)} +
+ ) + } +} + +export default PersonList \ No newline at end of file diff --git a/admin-panel/src/components/routes/EventPage.js b/admin-panel/src/components/routes/EventPage.js new file mode 100644 index 0000000..992cf1f --- /dev/null +++ b/admin-panel/src/components/routes/EventPage.js @@ -0,0 +1,38 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import {connect} from 'react-redux' + +import EventList from '../events/EventList' +import {loadEvents, eventsSelector, loadingSelector, errorSelector} from "../../ducks/events" +import Loader from '../common/Loader' + +class EventPage extends Component { + static propTypes = { + events: PropTypes.array, + loading: PropTypes.bool, + error: PropTypes.string + }; + + componentDidMount = () => { + this.props.loadEvents() + } + + render() { + const {events, loading, error} = this.props + + return ( +
+

Events

+ {loading && } + {error &&

{error}

} + {events && } +
+ ) + } +} + +export default connect(state => ({ + events: eventsSelector(state), + loading: loadingSelector(state), + error: errorSelector(state) +}), {loadEvents})(EventPage) \ 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..0061f5c --- /dev/null +++ b/admin-panel/src/components/routes/PersonPage.js @@ -0,0 +1,31 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import {connect} from 'react-redux' + +import {addPerson} from '../../ducks/people' +import NewPersonForm from '../people/NewPersonForm' +import PersonList from '../people/PersonList' +import {peopleSelector} from '../../ducks/people' + +class PersonPage extends Component { + static propTypes = { + persons: PropTypes.object, + addPerson: PropTypes.func.isRequired + }; + + render() { + const {persons, addPerson} = this.props + + return ( +
+ +

Add new person

+ +
+ ) + } +} + +export default connect(state => ({ + persons: peopleSelector(state) +}), {addPerson})(PersonPage) \ No newline at end of file diff --git a/admin-panel/src/components/routes/auth/index.js b/admin-panel/src/components/routes/auth/index.js index 91c9bab..f75ee3c 100644 --- a/admin-panel/src/components/routes/auth/index.js +++ b/admin-panel/src/components/routes/auth/index.js @@ -1,16 +1,22 @@ import React, { Component } from 'react' +import PropTypes from 'prop-types' import {Route, NavLink} from 'react-router-dom' import {connect} from 'react-redux' + import {signIn, signUp} from '../../../ducks/auth' +import {errorSelector, loadingSelector} from '../../../ducks/auth' import SignInForm from '../../auth/SignInForm' import SignUpForm from '../../auth/SignUpForm' class Auth extends Component { static propTypes = { - + authError: PropTypes.object, + authLoading: PropTypes.bool.isRequired }; render() { + const {authLoading, authError} = this.props; + return (

Auth page

@@ -18,7 +24,7 @@ class Auth extends Component {
  • Sign In
  • Sign Up
  • - } /> + } /> } />
    ) @@ -29,4 +35,7 @@ class Auth extends Component { } -export default connect(null, { signIn, signUp })(Auth) \ No newline at end of file +export default connect(state => ({ + authError: errorSelector(state), + authLoading: loadingSelector(state) +}), { signIn, signUp })(Auth) \ No newline at end of file diff --git a/admin-panel/src/components/user/NewUserForm.js b/admin-panel/src/components/user/NewUserForm.js new file mode 100644 index 0000000..605022d --- /dev/null +++ b/admin-panel/src/components/user/NewUserForm.js @@ -0,0 +1,50 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import {reduxForm, Field} from 'redux-form'; +import validator from 'email-validator'; + +import ErrorField from '../common/ErrorField'; + +class NewUserForm extends Component { + static propTypes = { + handleSubmit: PropTypes.func.isRequired + }; + + render() { + return ( +
    +

    Add user

    +
    +
    + First name: +
    +
    + Last name: +
    +
    + Email: +
    + +
    + +
    + ) + } +} + +const validate = ({ firstName, lastName, email }) => { + const errors = {}; + + if (!email) errors.email = 'Email is a required field'; + if (email && !validator.validate(email)) errors.email = 'Incorrect email format'; + + if (!firstName) errors.firstName = 'First name is a required field'; + if (!lastName) errors.lastName = 'Last name is a required field'; + + return errors; +} + +export default reduxForm({ + form: 'new-user', + validate +})(NewUserForm) \ No newline at end of file diff --git a/admin-panel/src/config.js b/admin-panel/src/config.js index 65bba00..202b263 100644 --- a/admin-panel/src/config.js +++ b/admin-panel/src/config.js @@ -1,14 +1,14 @@ -import firebase from 'firebase' +import firebase from 'firebase'; -export const appName = 'advreact-04-12' +export const appName = 'adv-react-8c4fb'; const config = { - apiKey: "AIzaSyCmDWlgYIhtEr1pWjgKYds3iXKWBl9wbjE", + apiKey: "AIzaSyCPlodrXJgpCXqDWklsFc_V5vHGiD6G2Uc", authDomain: `${appName}.firebaseapp.com`, databaseURL: `https://${appName}.firebaseio.com`, - projectId: appName, - storageBucket: "", - messagingSenderId: "95255462276" -} + projectId: {appName}, + storageBucket: "adv-react-8c4fb.appspot.com", + messagingSenderId: "47356959167" +}; -firebase.initializeApp(config) \ No newline at end of file +firebase.initializeApp(config); \ No newline at end of file diff --git a/admin-panel/src/ducks/auth.js b/admin-panel/src/ducks/auth.js index f031871..7ad6671 100644 --- a/admin-panel/src/ducks/auth.js +++ b/admin-panel/src/ducks/auth.js @@ -1,4 +1,6 @@ +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' @@ -8,10 +10,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 +35,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 +60,96 @@ 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() + const user = yield call([auth, auth.createUserWithEmailAndPassword], email, password) + + yield put({ + type: SIGN_UP_SUCCESS, + payload: {user} + }) + + } 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() + const user = yield apply(auth, auth.signInWithEmailAndPassword, [email, password]) + + yield put({ + type: SIGN_IN_SUCCESS, + payload: { user } + }) + + } catch (error) { + yield put({ + type: SIGN_IN_ERROR, + payload: { error } + }) + } +} + +export const saga = function * () { + yield all([ + takeEvery(SIGN_IN_REQUEST, signInSaga), + signUpSaga() + ]) +} \ 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..7aca323 --- /dev/null +++ b/admin-panel/src/ducks/auth.test.js @@ -0,0 +1,59 @@ +import {apply, take, call, put} from 'redux-saga/effects' +import { + signInSaga, + signUpSaga, + signUp, + SIGN_IN_REQUEST, + SIGN_IN_START, + SIGN_IN_SUCCESS, + SIGN_UP_REQUEST, + SIGN_UP_START, + SIGN_UP_SUCCESS +} from './auth' +import firebase from 'firebase' + +const email = 'veresku@gmail.com' +const password = 'qweasd' +const auth = firebase.auth() +const user = {id: '123', email: email} + +describe('sign in saga', () => { + it('should sing in', () => { + const action = { + type: SIGN_IN_REQUEST, + payload: {email, password} + } + + const generator = signInSaga(action) + + expect(generator.next().value).toEqual(put({ + type: SIGN_IN_START + })) + + expect(generator.next().value).toEqual(apply(auth, auth.signInWithEmailAndPassword, [email, password])) + + expect(generator.next(user).value).toEqual(put({ + type: SIGN_IN_SUCCESS, + payload: {user} + })) + + expect(generator.next().done).toBe(true) + }); +}); + +describe('sign up saga', () => { + it('should sing up', () => { + const generator = signUpSaga() + + expect(generator.next().value).toEqual(take(SIGN_UP_REQUEST)) + + expect(generator.next(signUp(email, password)).value).toEqual(put({type: SIGN_UP_START})) + + expect(generator.next().value).toEqual(call([auth, auth.createUserWithEmailAndPassword], email, password)) + + expect(generator.next(user).value).toEqual(put({ + type: SIGN_UP_SUCCESS, + payload: {user} + })) + }); +}); \ No newline at end of file diff --git a/admin-panel/src/ducks/events.js b/admin-panel/src/ducks/events.js new file mode 100644 index 0000000..c067334 --- /dev/null +++ b/admin-panel/src/ducks/events.js @@ -0,0 +1,113 @@ +import {appName} from '../config' +import {Record} from 'immutable' +import {put, all, take, call} from 'redux-saga/effects' +import {createSelector} from "reselect" +import firebase from 'firebase/index' + +/** + * Constants + * */ +export const moduleName = 'events' +const prefix = `${appName}/${moduleName}` +export const LOAD_EVENTS_REQUEST = `${prefix}/LOAD_EVENTS_REQUEST` +export const LOAD_EVENTS_START = `${prefix}/LOAD_EVENTS_START` +export const LOAD_EVENTS_SUCCESS = `${prefix}/LOAD_EVENTS_SUCCESS` +export const LOAD_EVENTS_ERROR = `${prefix}/LOAD_EVENTS_ERROR` + +/** + * Reducer + * */ +export const ReducerRecord = Record({ + data: null, + loading: false, + error: null +}) + +export default function reducer(state = new ReducerRecord(), action) { + const {type, payload} = action + + switch (type) { + case LOAD_EVENTS_START: + return state + .set('error', null) + .set('loading', true) + case LOAD_EVENTS_SUCCESS: + return state + .set('loading', false) + .set('data', payload) + case LOAD_EVENTS_ERROR: + return state + .set('loading', false) + .set('error', payload.error.message) + default: + return state + } +} + +/** + * Selectors + * */ +export const stateSelector = state => state[moduleName] +export const eventsSelector = createSelector(stateSelector, state => state.data) +export const loadingSelector = createSelector(stateSelector, state => state.loading) +export const errorSelector = createSelector(stateSelector, state => state.error) + +/** + * Action Creators + * */ + +export function loadEvents() { + return { + type: LOAD_EVENTS_REQUEST + } +} + +/** + * Sagas + */ +function getFBEvents(ref) { + return ref.once('value').then(function(snapshot) { + let events = []; + + snapshot.forEach(function(childSnapshot) { + events.push({ + id: childSnapshot.key, + ...childSnapshot.val() + }) + }); + return events; + }); +} + +export const loadEventsSaga = function * () { + while (true) { + yield take(LOAD_EVENTS_REQUEST) + + yield put({ + type: LOAD_EVENTS_START + }) + + try { + const db = firebase.database(); + const eventsRef = yield call([db, db.ref], 'events/'); + const events = yield call(getFBEvents, eventsRef) + + yield put({ + type: LOAD_EVENTS_SUCCESS, + payload: events + }) + + } catch (error) { + yield put({ + type: LOAD_EVENTS_ERROR, + payload: {error} + }) + } + } +} + +export const saga = function * () { + yield all([ + loadEventsSaga() + ]) +} \ 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..ca4894c --- /dev/null +++ b/admin-panel/src/ducks/people.js @@ -0,0 +1,77 @@ +import {appName} from '../config' +import {Record, List} from 'immutable' +import {put, call, all, takeEvery} from 'redux-saga/effects' +import {generateId} from './utils' +import {createSelector} from "reselect" + +/** + * 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 List([]) +}) + +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.update('entities', entities => entities.push(new PersonRecord(payload))) + + default: + return state + } +} + +/** + * Selectors + * */ +export const stateSelector = state => state[moduleName] +export const peopleSelector = createSelector(stateSelector, state => state.entities) + +/** + * Action Creators + * */ + +export function addPerson(person) { + return { + type: ADD_PERSON, + payload: { person } + } +} + +/** + * Sagas + */ + +export const addPersonSaga = function * (action) { + const { person } = action.payload + + const id = yield call(generateId) + + yield put({ + type: ADD_PERSON_SUCCESS, + payload: {id, ...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..e1bf76f --- /dev/null +++ b/admin-panel/src/ducks/people.test.js @@ -0,0 +1,32 @@ +import {call, put} from 'redux-saga/effects' +import {addPersonSaga, ADD_PERSON, ADD_PERSON_SUCCESS} from './people' +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 id = generateId() + + expect(generator.next(id).value).toEqual(put({ + type: ADD_PERSON_SUCCESS, + payload: {id, ...person} + })) + + expect(generator.next().done).toBe(true) + + }); +}); \ 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..f1e9445 --- /dev/null +++ b/admin-panel/src/ducks/utils.js @@ -0,0 +1,3 @@ +export function generateId() { + return Date.now() +} \ 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..fe902fe 100644 --- a/admin-panel/src/redux/index.js +++ b/admin-panel/src/redux/index.js @@ -1,13 +1,32 @@ -import {createStore, applyMiddleware} from 'redux' +import {createStore, applyMiddleware, compose} from 'redux' import logger from 'redux-logger' import {routerMiddleware} from 'react-router-redux' -import thunk from 'redux-thunk' +import createSagaMiddleware from 'redux-saga' import reducer from './reducer' import history from '../history' +import saga from './saga' -const store = createStore(reducer, applyMiddleware(thunk, routerMiddleware(history), logger)) +const sagaMiddleware = createSagaMiddleware() -//dev only -window.store = store +// If Redux DevTools Extension is installed use it, otherwise use Redux compose +/* eslint-disable no-underscore-dangle */ +const composeEnhancers = + process.env.NODE_ENV !== 'production' && + typeof window === 'object' && + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose; +/* eslint-enable */ + +const enhancer = composeEnhancers( + applyMiddleware(sagaMiddleware, routerMiddleware(history), logger) +); + +const store = createStore(reducer, enhancer) + +sagaMiddleware.run(saga) + +if (process.env.NODE_ENV !== 'production') { + window.store = store; +} export default store \ No newline at end of file diff --git a/admin-panel/src/redux/reducer.js b/admin-panel/src/redux/reducer.js index 34143fe..d593bc8 100644 --- a/admin-panel/src/redux/reducer.js +++ b/admin-panel/src/redux/reducer.js @@ -2,8 +2,12 @@ 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 peopleReducer, {moduleName as peopleModule} from '../ducks/people' +import eventsReducer, {moduleName as eventsModule} from '../ducks/events' export default combineReducers({ router, form, - [authModule]: authReducer + [authModule]: authReducer, + [peopleModule]: peopleReducer, + [eventsModule]: eventsReducer }) \ 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/yarn.lock b/admin-panel/yarn.lock index c9c597c..5e59e02 100644 --- a/admin-panel/yarn.lock +++ b/admin-panel/yarn.lock @@ -5497,6 +5497,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"