diff --git a/.codeclimate.yml b/.codeclimate.yml index 90ec5722..6dc0ff57 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -22,3 +22,5 @@ ratings: - "**.jsx" exclude_paths: - node_modules/ +- .next/ +- .gitattributes diff --git a/.yarnclean b/.yarnclean new file mode 100644 index 00000000..25bdd148 --- /dev/null +++ b/.yarnclean @@ -0,0 +1,42 @@ +# test directories +__tests__ +test +tests +powered-test + +# asset directories +docs +doc +website +images +assets + +# examples +example +examples + +# code coverage directories +coverage +.nyc_output + +# build scripts +Makefile +Gulpfile.js +Gruntfile.js + +# configs +.tern-project +.gitattributes +.editorconfig +.*ignore +.eslintrc +.jshintrc +.flowconfig +.documentup.json +.yarn-metadata.json +.*.yml +*.yml + +# misc +*.gz +*.md diff --git a/components/AuthFields/index.data.js b/components/AuthFields/index.data.js new file mode 100644 index 00000000..e34b5b8c --- /dev/null +++ b/components/AuthFields/index.data.js @@ -0,0 +1,53 @@ +import persist from '../../libraries/persist'; + +// Actions Naming +export const AUTH_SIGNIN = 'AUTH_SIGNIN'; +export const AUTH_SIGNOUT = 'AUTH_SIGNOUT'; +export const AUTH_SERVERERROR = 'AUTH_SERVERERROR'; + +// Initial State +const initialState = { + authenticated: false, + token: null, + error: null +}; + +// Reducer +const reducer = (state = initialState, action) => { + switch (action.type) { + case AUTH_SIGNIN: + return { + ...state, + authenticated: true, + token: action.token, + error: null + }; + case AUTH_SIGNOUT: + return { ...state, authenticated: false, token: null, error: null }; + case AUTH_SERVERERROR: + return { ...state, authenticated: false, error: action.error }; + default: + return state; + } +}; + +// Actions +const actions = {}; + +actions.signIn = token => ({ type: AUTH_SIGNIN, token }); +actions.signOut = () => ({ type: AUTH_SIGNOUT }); + +// Discpatchers +const dispatchers = {}; + +dispatchers.signIn = token => { + persist.willSetAccessToken(token); + return actions.signIn(token); +}; + +dispatchers.signOut = () => { + persist.willRemoveAccessToken(); + return actions.signOut(); +}; + +export { actions, reducer, dispatchers }; diff --git a/components/AuthFields/index.gql.js b/components/AuthFields/index.gql.js new file mode 100644 index 00000000..928d4bf4 --- /dev/null +++ b/components/AuthFields/index.gql.js @@ -0,0 +1,31 @@ +import { gql } from 'react-apollo'; + +export default {}; + +export const signIn = gql` + mutation signinUser($email: String!, $password: String!) { + signinUser(email: { email: $email, password: $password }) { + token + } + } +`; + +export const createUser = gql` + mutation createUser( + $firstName: String! + $lastName: String! + $email: String! + $password: String! + ) { + createUser( + firstName: $firstName + lastName: $lastName + authProvider: { email: { email: $email, password: $password } } + ) { + id + } + signinUser(email: { email: $email, password: $password }) { + token + } + } +`; diff --git a/components/AuthFields/index.js b/components/AuthFields/index.js new file mode 100644 index 00000000..bdfd3ba9 --- /dev/null +++ b/components/AuthFields/index.js @@ -0,0 +1,75 @@ +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; + +const AuthFields = props => { + const { + selectFields, + fields, + handleTouch, + handleChange, + handleSubmit, + touched, + errors + } = props; + const mapFields = fields.map(field => +
+ + {errors && +
+ {errors[field.attr.name]} +
} +
+ ); + const authMethod = + (selectFields === 'signinFields' && 'Sign In') || 'Sign Up'; + return ( +
+

+ {authMethod} +

+
+ {mapFields} +
+ +
+
+ ); +}; + +AuthFields.propTypes = { + selectFields: PropTypes.string.isRequired, + fields: PropTypes.array.isRequired, + handleTouch: PropTypes.func.isRequired, + handleChange: PropTypes.func.isRequired, + handleSubmit: PropTypes.func.isRequired, + touched: PropTypes.bool.isRequired, + errors: PropTypes.object.isRequired +}; + +export default styled(AuthFields)` + border-bottom: 1px solid #ececec; + padding-bottom: 20px; + margin-bottom: 20px; + + > h1 { + font-size: 20px; + } + + >input { + display: block; + margin-bottom: 10px; + } +`; diff --git a/components/AuthFields/index.validation.js b/components/AuthFields/index.validation.js new file mode 100644 index 00000000..0ece39cd --- /dev/null +++ b/components/AuthFields/index.validation.js @@ -0,0 +1,28 @@ +import { isStringEmpty, isEmail } from '../../libraries/validations'; + +export default values => { + const errors = {}; + const touched = true; + + if (isStringEmpty(values.firstName)) { + errors.firstName = 'Required'; + } + + if (isStringEmpty(values.lastName)) { + errors.lastName = 'Required'; + } + + if (isStringEmpty(values.email)) { + errors.email = 'Required'; + } else if (!isEmail(values.email)) { + errors.email = 'Invalid email address'; + } + + if (isStringEmpty(values.password)) { + errors.password = 'Required'; + } else if (values.password.length <= 3) { + errors.password = 'Must be at least 4 characters'; + } + + return { errors, touched }; +}; diff --git a/components/Header.js b/components/Header.js deleted file mode 100644 index 44ce447b..00000000 --- a/components/Header.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import LinkList from './LinkList'; - -const Header = ({ className, pathname }) => -
- -
; - -Header.propTypes = { - pathname: PropTypes.string.isRequired, - className: PropTypes.string.isRequired -}; - -export default styled(Header)` - margin-bottom: 25px; -`; diff --git a/components/Header/index.data.js b/components/Header/index.data.js new file mode 100644 index 00000000..8765d61c --- /dev/null +++ b/components/Header/index.data.js @@ -0,0 +1,14 @@ +import { connect } from 'react-redux'; +import { actions } from '../SignInForm/index.data'; + +const mapStateToProps = state => ({ + authenticated: state.auth.authenticated +}); + +const mapDispatchToProps = dispatch => ({ + logout() { + dispatch(actions.signout()); + } +}); + +export default comp => connect(mapStateToProps, mapDispatchToProps)(comp); diff --git a/components/Header/index.js b/components/Header/index.js new file mode 100644 index 00000000..c6092f67 --- /dev/null +++ b/components/Header/index.js @@ -0,0 +1,28 @@ +import PropTypes from 'prop-types'; +import styled from 'styled-components'; +import LinkList from '../LinkList'; +import connect from './index.data'; + +const Header = ({ className, pathname, authenticated, logout }) => +
+ +
; + +Header.defaultProps = { + authenticated: false +}; + +Header.propTypes = { + pathname: PropTypes.string.isRequired, + className: PropTypes.string.isRequired, + authenticated: PropTypes.bool.isRequired, + logout: PropTypes.func.isRequired +}; + +export default connect(styled(Header)` + margin-bottom: 25px; +`); diff --git a/components/LinkList.js b/components/LinkList.js index 1081fb1b..b0aed5ac 100644 --- a/components/LinkList.js +++ b/components/LinkList.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import styled from 'styled-components'; import { Router } from '../routes'; -const LinkList = ({ className, pathname }) => +const LinkList = ({ className, pathname, authenticated, logout }) =>