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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,32 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<style>
nav.submenu {
background: #eee;
padding: .5rem;
}

nav.submenu a {
margin-right: 1rem;
color: #333;
text-decoration: none;
}
nav.submenu a.active {
font-weight: bold;
}
.error-field {
margin-bottom: 1rem;
}
.members-ui {
display: flex;
}
.members-ui ul,
.members-ui .no-members {
width: 300px;
margin-right: 1rem;
}
</style>
</head>
<body>
<noscript>
Expand Down
44 changes: 44 additions & 0 deletions src/components/admin/MemberForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { Component } from 'react'
import { reduxForm, Field } from 'redux-form'
import emailValidator from 'email-validator'
import ErrorField from '../common/ErrorField'

class MemberForm extends Component {
render() {
return (
<form onSubmit={this.props.handleSubmit}>
<Field component={ErrorField} label="First name" name="firstName" />
<Field component={ErrorField} label="Last name" name="lastName" />
<Field component={ErrorField} label="E-mail" name="email" type="email" />
<button>Add member</button>
</form>
)
}
}

const validate = values => {
const { firstName, lastName, email } = values
const errors = {}

if (!firstName) {
errors.firstName = 'First name is required.'
}

if (!lastName) {
errors.lastName = 'Last name is required.'
}

if (!email) {
errors.email = 'Email is required.'
}
else if (!emailValidator.validate(email)) {
errors.email = 'Email is invalid.'
}

return errors
}

export default reduxForm({
form: 'member',
validate,
})(MemberForm)
8 changes: 5 additions & 3 deletions src/components/common/ErrorField.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ class ErrorField extends Component {
};

render() {
const {input, meta: {error, touched}, type, label} = this.props
const {input, meta, type, label} = this.props
const {error, touched} = meta || {}
const errorText = touched && error && <div style = {{color: 'red'}}>{error}</div>
return (
<div>
{label} <input {...input} type = {type} />
<div className="error-field">
{label}<br/>
<input {...input} type = {type} />
{errorText}
</div>
)
Expand Down
24 changes: 24 additions & 0 deletions src/components/common/Submenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'

class Submenu extends Component {
render() {
return (
<nav className="submenu">
{this.props.links.map(this.renderSubmenuItem.bind(this))}
</nav>
)
}

renderSubmenuItem(item, index) {
const { to, title } = item

return (
<NavLink key={index} to={to} activeClassName="active">
{title}
</NavLink>
)
}
}

export default Submenu
16 changes: 16 additions & 0 deletions src/components/routes/Admin.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import React, { Component } from 'react'
import Submenu from '../common/Submenu'
import { Route } from 'react-router-dom'
import People from './People'

class Admin extends Component {
static propTypes = {

};

render() {
const links = [{
to: '/admin/people',
title: 'People',
}, {
to: '/admin/stats',
title: 'Stats',
}, {
to: '/admin/whatever',
title: 'Whatever',
}]

return (
<div>
<h2>Admin Page</h2>
<Submenu links={links} />
<Route path="/admin/people" component={People} />
</div>
)
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/routes/Auth.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import {Route, NavLink} from 'react-router-dom'
import {connect} from 'react-redux'
import {signUp} from '../../ducks/auth'
import {signUp, signIn} from '../../ducks/auth'
import SignIn from '../auth/SignIn'
import SignUp from '../auth/SignUp'

Expand All @@ -22,8 +22,8 @@ class AuthPage extends Component {
)
}

handleSignIn = (values) => console.log('---', 'sign in', values)
handleSignIn = ({email, password}) => this.props.signIn(email, password)
handleSignUp = ({email, password}) => this.props.signUp(email, password)
}

export default connect(null, { signUp })(AuthPage)
export default connect(null, { signUp, signIn })(AuthPage)
68 changes: 68 additions & 0 deletions src/components/routes/People.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import MemberForm from '../admin/MemberForm'
import { addMember, membersSelector } from '../../ducks/people'

class People extends Component {
get members() {
return this.props.members
}

get hasMembers() {
return this.members.length > 0
}

curryAddMember() {
return values => {
const { firstName, lastName, email } = values

this.props.addMember(firstName, lastName, email)
}
}

render() {
return (
<div>
<h1>People</h1>
<div className="members-ui">
{this.renderMembers()}
{this.renderAddForm()}
</div>
</div>
)
}

renderMembers() {
if (!this.hasMembers) {
return <p className="no-members">There is no members yet.</p>
}

return (
<ul>
{this.members.map(this.renderMember.bind(this))}
</ul>
)
}

renderMember(item) {
const { firstName, lastName, email } = item

return (
<li>
{firstName} {lastName} <a href={`mailto:${email}`}>{email}</a>
</li>
)
}

renderAddForm() {
return <MemberForm onSubmit={this.curryAddMember()} />
}
}

export default connect(state => {
return {
members: membersSelector(state)
}
}, {
addMember,
})(People)
6 changes: 3 additions & 3 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import firebase from 'firebase'

export const appName = 'advreact-1610'
export const appName = 'advreact-1610-9cb0c'

firebase.initializeApp({
apiKey: "AIzaSyB31xpTtp4Jln_hb2kAbE4PGf6Mi8EgLyA",
apiKey: "AIzaSyBNaOQkLL75ZGoeaNtqbe63wEjjWLzLOPY",
authDomain: `${appName}.firebaseapp.com`,
databaseURL: `https://${appName}.firebaseio.com`,
projectId: appName,
storageBucket: "",
messagingSenderId: "397157634637"
messagingSenderId: "266748171955"
})
28 changes: 28 additions & 0 deletions src/ducks/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ 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`

export const SIGN_IN_START = `${prefix}/SIGN_IN_START`
export const SIGN_IN_SUCCESS = `${prefix}/SIGN_IN_SUCCESS`
export const SIGN_IN_ERROR = `${prefix}/SIGN_IN_ERROR`


/**
* Reducer
Expand All @@ -29,6 +32,7 @@ export default function reducer(state = new ReducerRecord(), action) {

switch (type) {
case SIGN_UP_START:
case SIGN_IN_START:
return state.set('loading', true)

case SIGN_UP_SUCCESS:
Expand All @@ -37,6 +41,12 @@ export default function reducer(state = new ReducerRecord(), action) {
.set('user', payload.user)
.set('loading', false)

case SIGN_UP_ERROR:
case SIGN_IN_ERROR:
return state
.set('error', payload.error)
.set('loading', false)

default:
return state
}
Expand Down Expand Up @@ -70,6 +80,24 @@ export function signUp(email, password) {
}
}

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_ERROR,
payload: { error }
}))
}
}

firebase.auth().onAuthStateChanged(user => {
if (!user) return

Expand Down
47 changes: 47 additions & 0 deletions src/ducks/people.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {Record} from 'immutable'
import {reset} from 'redux-form';
import {appName} from '../config'
import {createSelector} from 'reselect'

export const moduleName = 'people'

const prefix = `${appName}/${moduleName}`

/* constants */
export const ADD_MEMBER = `${prefix}/ADD_MEMBER`

/* reducer */
const ReducerRecord = Record({
items: [],
Copy link
Owner

Choose a reason for hiding this comment

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

массив не самая удобная структура для работы, возьми лучше Map

})

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

switch (type) {
case ADD_MEMBER:
return state
.set('items', state.get('items').concat(payload.user))

default:
return state
}
}

/* action creators */
export function addMember(firstName, lastName, email) {
const user = {firstName, lastName, email}

return dispatch => {
dispatch({
type: ADD_MEMBER,
payload: {user},
})

dispatch(reset('member'))
}
}

/* selectors */
export const stateSelector = state => state[moduleName]
export const membersSelector = createSelector(stateSelector, state => state.items)
6 changes: 4 additions & 2 deletions src/redux/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})