From d8b4d506fbd4fa32f0bc744966975fd06cddf511 Mon Sep 17 00:00:00 2001 From: veresku Date: Sat, 9 Dec 2017 21:10:15 +0100 Subject: [PATCH 1/6] done --- admin-panel/package.json | 1 + admin-panel/src/components/auth/SignInForm.js | 10 +++- admin-panel/src/components/routes/Admin.js | 17 +++++- .../src/components/routes/auth/index.js | 15 ++++- .../src/components/user/NewUserForm.js | 50 ++++++++++++++++ admin-panel/src/config.js | 16 +++--- admin-panel/src/ducks/auth.js | 11 ++++ admin-panel/src/ducks/user.js | 57 +++++++++++++++++++ admin-panel/src/redux/index.js | 35 ++++++++---- 9 files changed, 188 insertions(+), 24 deletions(-) create mode 100644 admin-panel/src/components/user/NewUserForm.js create mode 100644 admin-panel/src/ducks/user.js diff --git a/admin-panel/package.json b/admin-panel/package.json index 5fbeba9..d40efcb 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", diff --git a/admin-panel/src/components/auth/SignInForm.js b/admin-panel/src/components/auth/SignInForm.js index 3033ef6..0cd7b78 100644 --- a/admin-panel/src/components/auth/SignInForm.js +++ b/admin-panel/src/components/auth/SignInForm.js @@ -1,12 +1,16 @@ import React, { Component } from 'react' import {reduxForm, Field} from 'redux-form' +import PropTypes from "prop-types" class SignInForm extends Component { static propTypes = { - + authError: PropTypes.object, + authLoading: PropTypes.bool.isRequired }; render() { + const {authError, authLoading} = this.props; + return (

Sign In

@@ -17,6 +21,10 @@ class SignInForm extends Component {
password:
+ + {authLoading &&

Loading...

} + {authError &&

{authError.message}

} + diff --git a/admin-panel/src/components/routes/Admin.js b/admin-panel/src/components/routes/Admin.js index 952248e..2b19d74 100644 --- a/admin-panel/src/components/routes/Admin.js +++ b/admin-panel/src/components/routes/Admin.js @@ -1,4 +1,9 @@ -import React, { Component } from 'react' +import React, { Component } from 'react'; +import {Route, NavLink} from 'react-router-dom'; +import {connect} from 'react-redux'; + +import {addUser} from '../../ducks/user'; +import NewUserForm from '../../components/user/NewUserForm'; class Admin extends Component { static propTypes = { @@ -9,9 +14,17 @@ class Admin extends Component { return (

Admin Page

+
    +
  • Create user
  • +
+ } />
) } + + onAddNewUser = ({firstName, lastName, email}) => { + this.props.addUser(firstName, lastName, email); + } } -export default Admin \ No newline at end of file +export default connect(null, {addUser})(Admin); \ 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..53346a6 100644 --- a/admin-panel/src/ducks/auth.js +++ b/admin-panel/src/ducks/auth.js @@ -10,6 +10,7 @@ const prefix = `${appName}/${moduleName}` export const SIGN_IN_START = `${prefix}/SIGN_IN_START` export const SIGN_IN_SUCCESS = `${prefix}/SIGN_IN_SUCCESS` +export const SIGN_IN_FAILURE = `${prefix}/SIGN_IN_FAILURE` export const SIGN_UP_START = `${prefix}/SIGN_UP_START` export const SIGN_UP_SUCCESS = `${prefix}/SIGN_UP_SUCCESS` @@ -35,6 +36,10 @@ export default function reducer(state = new ReducerRecord(), action) { return state .set('loading', false) .set('user', payload.user) + case SIGN_IN_FAILURE: + return state + .set('loading', false) + .set('error', payload.error) default: return state } @@ -45,6 +50,8 @@ export default function reducer(state = new ReducerRecord(), action) { * */ export const userSelector = state => state[moduleName].user +export const errorSelector = state => state[moduleName].error +export const loadingSelector = state => state[moduleName].loading /** * Action Creators @@ -61,6 +68,10 @@ export function signIn(email, password) { type: SIGN_IN_SUCCESS, payload: { user } })) + .catch(error => dispatch({ + type: SIGN_IN_FAILURE, + payload: { error } + })) } } diff --git a/admin-panel/src/ducks/user.js b/admin-panel/src/ducks/user.js new file mode 100644 index 0000000..966e694 --- /dev/null +++ b/admin-panel/src/ducks/user.js @@ -0,0 +1,57 @@ +import {appName} from '../config'; +import {Record} from 'immutable'; +import firebase from 'firebase'; + +/** + * Constants + * */ +export const moduleName = 'user'; +const prefix = `${appName}/${moduleName}`; + +export const ADD_USER_START = `${prefix}/ADD_USER_START`; +export const ADD_USER_SUCCESS = `${prefix}/ADD_USER_SUCCESS`; + +/** + * Reducer + * */ +export const ReducerRecord = Record({ + user: null, + loading: false, + error: null +}) + +export default function reducer(state = new ReducerRecord(), action) { + const {type, payload} = action + + switch (type) { + case ADD_USER_START: + return state.set('loading', true) + + case ADD_USER_SUCCESS: + return state + .set('loading', false) + .set('user', payload.user) + default: + return state + } +} + +/** + * Selectors + * */ + +export const userSelector = state => state[moduleName].user + +/** + * Action Creators + * */ + +export function addUser(firstName, lastName, email) { + return (dispatch) => { + dispatch({ + type: ADD_USER_START + }) + + console.log('addUser', firstName, lastName, email); + } +} \ No newline at end of file diff --git a/admin-panel/src/redux/index.js b/admin-panel/src/redux/index.js index 1a37261..768e08c 100644 --- a/admin-panel/src/redux/index.js +++ b/admin-panel/src/redux/index.js @@ -1,13 +1,28 @@ -import {createStore, applyMiddleware} from 'redux' -import logger from 'redux-logger' -import {routerMiddleware} from 'react-router-redux' -import thunk from 'redux-thunk' -import reducer from './reducer' -import history from '../history' +import {createStore, applyMiddleware, compose} from 'redux'; +import thunk from 'redux-thunk'; +import logger from 'redux-logger'; +import {routerMiddleware} from 'react-router-redux'; -const store = createStore(reducer, applyMiddleware(thunk, routerMiddleware(history), logger)) +import reducer from './reducer'; +import history from '../history'; -//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 */ -export default store \ No newline at end of file +const enhancer = composeEnhancers( + applyMiddleware(thunk, routerMiddleware(history), logger) +); + +const store = createStore(reducer, enhancer); + +if (process.env.NODE_ENV !== 'production') { + window.store = store; +} + +export default store; From 81b44c6093fa8c337253ca118ab259d4d6677246 Mon Sep 17 00:00:00 2001 From: veresku Date: Mon, 11 Dec 2017 19:51:44 +0100 Subject: [PATCH 2/6] Merge branch 'master' of https://github.com/romabelka/advreact_04_12 --- .gitignore | 23 + admin-panel/README.md | 7 +- admin-panel/package.json | 1 + admin-panel/src/App.js | 23 - admin-panel/src/Root.js | 2 +- admin-panel/src/components/App.js | 29 + admin-panel/src/components/auth/SignInForm.js | 33 +- admin-panel/src/components/auth/SignUpForm.js | 21 +- .../src/components/common/ErrorField.js | 29 +- admin-panel/src/components/common/Loader.js | 12 + .../src/components/people/NewPersonForm.js | 40 + .../src/components/routes/PersonPage.js | 21 + admin-panel/src/ducks/auth.js | 124 +- admin-panel/src/ducks/people.js | 74 + admin-panel/src/ducks/people.test.js | 32 + admin-panel/src/ducks/utils.js | 3 + admin-panel/src/index.js | 1 + admin-panel/src/mocks/conferences.js | 2384 +++++++++++++++++ admin-panel/src/mocks/index.js | 9 + admin-panel/src/redux/index.js | 36 +- admin-panel/src/redux/reducer.js | 4 +- admin-panel/src/redux/saga.js | 10 + admin-panel/yarn.lock | 4 + 23 files changed, 2797 insertions(+), 125 deletions(-) create mode 100644 .gitignore delete mode 100644 admin-panel/src/App.js create mode 100644 admin-panel/src/components/App.js create mode 100644 admin-panel/src/components/common/Loader.js create mode 100644 admin-panel/src/components/people/NewPersonForm.js create mode 100644 admin-panel/src/components/routes/PersonPage.js create mode 100644 admin-panel/src/ducks/people.js create mode 100644 admin-panel/src/ducks/people.test.js create mode 100644 admin-panel/src/ducks/utils.js create mode 100644 admin-panel/src/mocks/conferences.js create mode 100644 admin-panel/src/mocks/index.js create mode 100644 admin-panel/src/redux/saga.js 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 d40efcb..62d5d33 100644 --- a/admin-panel/package.json +++ b/admin-panel/package.json @@ -18,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..6abd045 --- /dev/null +++ b/admin-panel/src/components/App.js @@ -0,0 +1,29 @@ +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' + +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/auth/SignInForm.js b/admin-panel/src/components/auth/SignInForm.js index 0cd7b78..df7f35b 100644 --- a/admin-panel/src/components/auth/SignInForm.js +++ b/admin-panel/src/components/auth/SignInForm.js @@ -1,38 +1,35 @@ import React, { Component } from 'react' +import {connect} from 'react-redux' import {reduxForm, Field} from 'redux-form' -import PropTypes from "prop-types" +import ErrorField from '../common/ErrorField' +import Loader from '../common/Loader' +import {loadingSelector, errorSelector} from '../../ducks/auth' class SignInForm extends Component { static propTypes = { - authError: PropTypes.object, - authLoading: PropTypes.bool.isRequired + }; render() { - const {authError, authLoading} = this.props; - + const {loading, authError} = this.props return (

    Sign In

    -
    - email: -
    -
    - password: -
    - - {authLoading &&

    Loading...

    } - {authError &&

    {authError.message}

    } - + + + {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/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/routes/PersonPage.js b/admin-panel/src/components/routes/PersonPage.js new file mode 100644 index 0000000..fd69263 --- /dev/null +++ b/admin-panel/src/components/routes/PersonPage.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import {connect} from 'react-redux' +import {addPerson} from '../../ducks/people' +import NewPersonForm from '../people/NewPersonForm' + +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/ducks/auth.js b/admin-panel/src/ducks/auth.js index 53346a6..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,11 +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_FAILURE = `${prefix}/SIGN_IN_FAILURE` +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 @@ -29,17 +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_FAILURE: + + case SIGN_IN_ERROR: + case SIGN_UP_ERROR: return state .set('loading', false) - .set('error', payload.error) + .set('error', payload.error.message) + default: return state } @@ -49,49 +60,96 @@ export default function reducer(state = new ReducerRecord(), action) { * Selectors * */ -export const userSelector = state => state[moduleName].user -export const errorSelector = state => state[moduleName].error -export const loadingSelector = state => state[moduleName].loading +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 } - })) - .catch(error => dispatch({ - type: SIGN_IN_FAILURE, - payload: { error } - })) +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/people.js b/admin-panel/src/ducks/people.js new file mode 100644 index 0000000..52bce70 --- /dev/null +++ b/admin-panel/src/ducks/people.js @@ -0,0 +1,74 @@ +import {appName} from '../config' +import {Record, List} from 'immutable' +import {put, call, all, takeEvery} from 'redux-saga/effects' +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 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.person))) + + default: + return state + } +} + +/** + * Selectors + * */ + +/** + * 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 768e08c..b69ed29 100644 --- a/admin-panel/src/redux/index.js +++ b/admin-panel/src/redux/index.js @@ -1,28 +1,18 @@ -import {createStore, applyMiddleware, compose} from 'redux'; -import thunk from 'redux-thunk'; -import logger from 'redux-logger'; -import {routerMiddleware} from 'react-router-redux'; +import {createStore, applyMiddleware} from 'redux' +import logger from 'redux-logger' +import {routerMiddleware} from 'react-router-redux' +import createSagaMiddleware from 'redux-saga' +import reducer from './reducer' +import history from '../history' +import saga from './saga' -import reducer from './reducer'; -import history from '../history'; +const sagaMiddleware = createSagaMiddleware() -// 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 store = createStore(reducer, applyMiddleware(sagaMiddleware, routerMiddleware(history), logger)) -const enhancer = composeEnhancers( - applyMiddleware(thunk, routerMiddleware(history), logger) -); +sagaMiddleware.run(saga) -const store = createStore(reducer, enhancer); +//dev only +window.store = store -if (process.env.NODE_ENV !== 'production') { - window.store = store; -} - -export default 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..cc6e0aa 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 peopleReducer, {moduleName as peopleModule} from '../ducks/people' export default combineReducers({ router, form, - [authModule]: authReducer + [authModule]: authReducer, + [peopleModule]: peopleReducer }) \ 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..4a6c841 --- /dev/null +++ b/admin-panel/src/redux/saga.js @@ -0,0 +1,10 @@ +import {all} from 'redux-saga/effects' +import {saga as peopleSaga} from '../ducks/people' +import {saga as authSaga} from '../ducks/auth' + +export default function * rootSaga() { + yield all([ + peopleSaga(), + authSaga() + ]) +} \ 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" From 8c9f77f167b9488ca3dd90bea75f53c59cdd0bb9 Mon Sep 17 00:00:00 2001 From: veresku Date: Wed, 13 Dec 2017 12:41:16 +0100 Subject: [PATCH 3/6] Merge branch 'master' of https://github.com/romabelka/advreact_04_12 Homework2 is done --- admin-panel/src/components/App.js | 3 + .../src/components/events/EventList.js | 73 +++++++++++++++++++ .../src/components/people/NewPersonForm.js | 8 ++ .../src/components/people/PersonItem.js | 22 ++++++ .../src/components/people/PersonList.js | 37 ++++++++++ admin-panel/src/components/routes/Admin.js | 17 +---- .../src/components/routes/EventPage.js | 20 +++++ .../src/components/routes/PersonPage.js | 16 +++- admin-panel/src/ducks/auth.test.js | 59 +++++++++++++++ admin-panel/src/ducks/people.js | 5 +- admin-panel/src/ducks/user.js | 57 --------------- admin-panel/src/redux/index.js | 22 +++++- 12 files changed, 259 insertions(+), 80 deletions(-) create mode 100644 admin-panel/src/components/events/EventList.js create mode 100644 admin-panel/src/components/people/PersonItem.js create mode 100644 admin-panel/src/components/people/PersonList.js create mode 100644 admin-panel/src/components/routes/EventPage.js create mode 100644 admin-panel/src/ducks/auth.test.js delete mode 100644 admin-panel/src/ducks/user.js diff --git a/admin-panel/src/components/App.js b/admin-panel/src/components/App.js index 6abd045..62fffd3 100644 --- a/admin-panel/src/components/App.js +++ b/admin-panel/src/components/App.js @@ -4,6 +4,7 @@ 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 = { @@ -17,9 +18,11 @@ class App extends Component {
    • admin
    • people
    • +
    • events
    +
    ) diff --git a/admin-panel/src/components/events/EventList.js b/admin-panel/src/components/events/EventList.js new file mode 100644 index 0000000..cc7e702 --- /dev/null +++ b/admin-panel/src/components/events/EventList.js @@ -0,0 +1,73 @@ +import React, {Component} from 'react' +import firebase from 'firebase/index' + +class EventList extends Component { + static propTypes = { + + }; + + state = { + events: null + } + + componentDidMount = () => { + const eventsRef = firebase.database().ref('events/'); + const _self = this; + + eventsRef.on('value', (snapshot) => { + let events = [] + + snapshot.forEach((childSnapshot) => { + events.push({ + id: childSnapshot.key, + ...childSnapshot.val() + }) + }); + + _self.setState({events: events}); + }); + } + + getEventList = (events) => { + return ( + + + + + + + + + + + + + { + events.map(event => + + + + + + + + + ) + } + +
    MonthDeadlineTitleURLWhenWhere
    {event.month}{event.submissionDeadline}{event.title}{event.url}{event.when}{event.where}
    + ) + } + + render() { + const {events} = this.state + + return ( +
    + {!events ?

    List is empty

    : this.getEventList(events)} +
    + ) + } +} + +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 index 6707eb7..4b2101b 100644 --- a/admin-panel/src/components/people/NewPersonForm.js +++ b/admin-panel/src/components/people/NewPersonForm.js @@ -8,6 +8,14 @@ class NewPersonForm extends Component { }; + componentWillReceiveProps = (nextProps) => { + const {submitSucceeded, reset} = this.props; + + if (nextProps.submitSucceeded === true && nextProps.submitSucceeded !== submitSucceeded) { + reset('person'); + } + } + render() { return (
    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/Admin.js b/admin-panel/src/components/routes/Admin.js index 2b19d74..952248e 100644 --- a/admin-panel/src/components/routes/Admin.js +++ b/admin-panel/src/components/routes/Admin.js @@ -1,9 +1,4 @@ -import React, { Component } from 'react'; -import {Route, NavLink} from 'react-router-dom'; -import {connect} from 'react-redux'; - -import {addUser} from '../../ducks/user'; -import NewUserForm from '../../components/user/NewUserForm'; +import React, { Component } from 'react' class Admin extends Component { static propTypes = { @@ -14,17 +9,9 @@ class Admin extends Component { return (

    Admin Page

    -
      -
    • Create user
    • -
    - } />
    ) } - - onAddNewUser = ({firstName, lastName, email}) => { - this.props.addUser(firstName, lastName, email); - } } -export default connect(null, {addUser})(Admin); \ No newline at end of file +export default Admin \ 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..6d8c1b8 --- /dev/null +++ b/admin-panel/src/components/routes/EventPage.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' + +import EventList from '../events/EventList' + +class EventPage extends Component { + static propTypes = { + + }; + + render() { + return ( +
    +

    Events

    + +
    + ) + } +} + +export default 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 index fd69263..0061f5c 100644 --- a/admin-panel/src/components/routes/PersonPage.js +++ b/admin-panel/src/components/routes/PersonPage.js @@ -1,21 +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(null, {addPerson})(PersonPage) \ No newline at end of file +export default connect(state => ({ + persons: peopleSelector(state) +}), {addPerson})(PersonPage) \ 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/people.js b/admin-panel/src/ducks/people.js index 52bce70..ca4894c 100644 --- a/admin-panel/src/ducks/people.js +++ b/admin-panel/src/ducks/people.js @@ -2,6 +2,7 @@ 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 @@ -30,7 +31,7 @@ export default function reducer(state = new ReducerState(), action) { switch (type) { case ADD_PERSON_SUCCESS: - return state.update('entities', entities => entities.push(new PersonRecord(payload.person))) + return state.update('entities', entities => entities.push(new PersonRecord(payload))) default: return state @@ -40,6 +41,8 @@ export default function reducer(state = new ReducerState(), action) { /** * Selectors * */ +export const stateSelector = state => state[moduleName] +export const peopleSelector = createSelector(stateSelector, state => state.entities) /** * Action Creators diff --git a/admin-panel/src/ducks/user.js b/admin-panel/src/ducks/user.js deleted file mode 100644 index 966e694..0000000 --- a/admin-panel/src/ducks/user.js +++ /dev/null @@ -1,57 +0,0 @@ -import {appName} from '../config'; -import {Record} from 'immutable'; -import firebase from 'firebase'; - -/** - * Constants - * */ -export const moduleName = 'user'; -const prefix = `${appName}/${moduleName}`; - -export const ADD_USER_START = `${prefix}/ADD_USER_START`; -export const ADD_USER_SUCCESS = `${prefix}/ADD_USER_SUCCESS`; - -/** - * Reducer - * */ -export const ReducerRecord = Record({ - user: null, - loading: false, - error: null -}) - -export default function reducer(state = new ReducerRecord(), action) { - const {type, payload} = action - - switch (type) { - case ADD_USER_START: - return state.set('loading', true) - - case ADD_USER_SUCCESS: - return state - .set('loading', false) - .set('user', payload.user) - default: - return state - } -} - -/** - * Selectors - * */ - -export const userSelector = state => state[moduleName].user - -/** - * Action Creators - * */ - -export function addUser(firstName, lastName, email) { - return (dispatch) => { - dispatch({ - type: ADD_USER_START - }) - - console.log('addUser', firstName, lastName, email); - } -} \ No newline at end of file diff --git a/admin-panel/src/redux/index.js b/admin-panel/src/redux/index.js index b69ed29..fe902fe 100644 --- a/admin-panel/src/redux/index.js +++ b/admin-panel/src/redux/index.js @@ -1,4 +1,4 @@ -import {createStore, applyMiddleware} from 'redux' +import {createStore, applyMiddleware, compose} from 'redux' import logger from 'redux-logger' import {routerMiddleware} from 'react-router-redux' import createSagaMiddleware from 'redux-saga' @@ -8,11 +8,25 @@ import saga from './saga' const sagaMiddleware = createSagaMiddleware() -const store = createStore(reducer, applyMiddleware(sagaMiddleware, routerMiddleware(history), logger)) +// 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) -//dev only -window.store = store +if (process.env.NODE_ENV !== 'production') { + window.store = store; +} export default store \ No newline at end of file From cb4f683ea6642eb06368ec20df26700136a7c30d Mon Sep 17 00:00:00 2001 From: veresku Date: Wed, 13 Dec 2017 14:44:55 +0100 Subject: [PATCH 4/6] Merge branch 'master' of https://github.com/romabelka/advreact_04_12 remove _self --- admin-panel/src/components/events/EventList.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/admin-panel/src/components/events/EventList.js b/admin-panel/src/components/events/EventList.js index cc7e702..eac0d12 100644 --- a/admin-panel/src/components/events/EventList.js +++ b/admin-panel/src/components/events/EventList.js @@ -12,7 +12,6 @@ class EventList extends Component { componentDidMount = () => { const eventsRef = firebase.database().ref('events/'); - const _self = this; eventsRef.on('value', (snapshot) => { let events = [] @@ -24,7 +23,7 @@ class EventList extends Component { }) }); - _self.setState({events: events}); + this.setState({events: events}); }); } From 5504a4c55b5a57961d3b7375e4c66e03aad2bb46 Mon Sep 17 00:00:00 2001 From: veresku Date: Wed, 13 Dec 2017 18:11:03 +0100 Subject: [PATCH 5/6] add saga --- .../src/components/events/EventList.js | 50 ++------ .../src/components/routes/EventPage.js | 24 +++- admin-panel/src/ducks/events.js | 115 ++++++++++++++++++ admin-panel/src/redux/reducer.js | 4 +- admin-panel/src/redux/saga.js | 4 +- 5 files changed, 152 insertions(+), 45 deletions(-) create mode 100644 admin-panel/src/ducks/events.js diff --git a/admin-panel/src/components/events/EventList.js b/admin-panel/src/components/events/EventList.js index eac0d12..42aa12b 100644 --- a/admin-panel/src/components/events/EventList.js +++ b/admin-panel/src/components/events/EventList.js @@ -1,44 +1,24 @@ import React, {Component} from 'react' -import firebase from 'firebase/index' class EventList extends Component { static propTypes = { }; - state = { - events: null - } - - componentDidMount = () => { - const eventsRef = firebase.database().ref('events/'); - - eventsRef.on('value', (snapshot) => { - let events = [] - - snapshot.forEach((childSnapshot) => { - events.push({ - id: childSnapshot.key, - ...childSnapshot.val() - }) - }); - - this.setState({events: events}); - }); - } + render() { + const {events} = this.props - getEventList = (events) => { return ( - - - - - - - - + + + + + + + + { @@ -57,16 +37,6 @@ class EventList extends Component {
    MonthDeadlineTitleURLWhenWhere
    MonthDeadlineTitleURLWhenWhere
    ) } - - render() { - const {events} = this.state - - return ( -
    - {!events ?

    List is empty

    : this.getEventList(events)} -
    - ) - } } export default EventList \ No newline at end of file diff --git a/admin-panel/src/components/routes/EventPage.js b/admin-panel/src/components/routes/EventPage.js index 6d8c1b8..992cf1f 100644 --- a/admin-panel/src/components/routes/EventPage.js +++ b/admin-panel/src/components/routes/EventPage.js @@ -1,20 +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 EventPage \ No newline at end of file +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/ducks/events.js b/admin-panel/src/ducks/events.js new file mode 100644 index 0000000..5867a15 --- /dev/null +++ b/admin-panel/src/ducks/events.js @@ -0,0 +1,115 @@ +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; + }).then(function (events) { + 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/redux/reducer.js b/admin-panel/src/redux/reducer.js index cc6e0aa..d593bc8 100644 --- a/admin-panel/src/redux/reducer.js +++ b/admin-panel/src/redux/reducer.js @@ -3,9 +3,11 @@ 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, - [peopleModule]: peopleReducer + [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 index 4a6c841..d9bb32f 100644 --- a/admin-panel/src/redux/saga.js +++ b/admin-panel/src/redux/saga.js @@ -1,10 +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() + authSaga(), + eventsSaga() ]) } \ No newline at end of file From 364ff1a9c5fdde439768170ddd52671113cc92f6 Mon Sep 17 00:00:00 2001 From: veresku Date: Wed, 13 Dec 2017 18:37:03 +0100 Subject: [PATCH 6/6] remove then --- admin-panel/src/ducks/events.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/admin-panel/src/ducks/events.js b/admin-panel/src/ducks/events.js index 5867a15..c067334 100644 --- a/admin-panel/src/ducks/events.js +++ b/admin-panel/src/ducks/events.js @@ -76,8 +76,6 @@ function getFBEvents(ref) { }) }); return events; - }).then(function (events) { - return events; }); }