diff --git a/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.ttf b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.ttf new file mode 100644 index 000000000..9bf9b4e97 Binary files /dev/null and b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.ttf differ diff --git a/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.woff b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.woff new file mode 100644 index 000000000..1247ee094 Binary files /dev/null and b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.woff differ diff --git a/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.woff2 b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.woff2 new file mode 100644 index 000000000..cb999df7b Binary files /dev/null and b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-bold.woff2 differ diff --git a/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.ttf b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.ttf new file mode 100644 index 000000000..aebcf1421 Binary files /dev/null and b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.ttf differ diff --git a/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.woff b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.woff new file mode 100644 index 000000000..97490be84 Binary files /dev/null and b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.woff differ diff --git a/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.woff2 b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.woff2 new file mode 100644 index 000000000..e2a23e488 Binary files /dev/null and b/client-mvp-04/src/assets/fonts/open-sans/opensans-italic-semibold.woff2 differ diff --git a/client-mvp-04/src/components/common/button/button.js b/client-mvp-04/src/components/common/button/button.js index 352c887c4..f477d610f 100644 --- a/client-mvp-04/src/components/common/button/button.js +++ b/client-mvp-04/src/components/common/button/button.js @@ -1,9 +1,23 @@ import React from 'react'; import './button.scss'; -const Button = ({ content, className }) => { +const Button = ({ + content, + className, + disabled, + onClick, + type, + dataTestid, +}) => { + if (!dataTestid) dataTestid = 'button'; return ( - ); diff --git a/client-mvp-04/src/components/common/errorMessage/errorMessage.js b/client-mvp-04/src/components/common/errorMessage/errorMessage.js new file mode 100644 index 000000000..4eef6e308 --- /dev/null +++ b/client-mvp-04/src/components/common/errorMessage/errorMessage.js @@ -0,0 +1,11 @@ +import React from 'react'; + +const ErrorMessage = ({ content }) => { + return ( +

+ {content} +

+ ); +}; + +export default ErrorMessage; diff --git a/client-mvp-04/src/components/common/errorMessage/errorMessage.test.js b/client-mvp-04/src/components/common/errorMessage/errorMessage.test.js new file mode 100644 index 000000000..e1016876c --- /dev/null +++ b/client-mvp-04/src/components/common/errorMessage/errorMessage.test.js @@ -0,0 +1,15 @@ +import React from 'react'; +import ErrorMessage from './errorMessage'; +import { render, screen } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; + +describe('Error Message', () => { + test('Should render with text content and className', () => { + render(, { + wrapper: BrowserRouter, + }); + expect(screen.getByTestId('error-message')).toBeInTheDocument(); + expect(screen.getByText('Test content')).toBeInTheDocument(); + expect(screen.getByTestId('error-message')).toHaveClass('error-message'); + }); +}); diff --git a/client-mvp-04/src/components/common/input/input.js b/client-mvp-04/src/components/common/input/input.js new file mode 100644 index 000000000..ccbd8bbab --- /dev/null +++ b/client-mvp-04/src/components/common/input/input.js @@ -0,0 +1,19 @@ +import React from 'react'; + +const Input = ({ placeholder, type, onChange, autoComplete, dataTestid }) => { + if (!dataTestid) dataTestid = 'default-input'; + return ( +
+ +
+ ); +}; + +export default Input; diff --git a/client-mvp-04/src/components/common/input/input.test.js b/client-mvp-04/src/components/common/input/input.test.js new file mode 100644 index 000000000..e67fdc6c2 --- /dev/null +++ b/client-mvp-04/src/components/common/input/input.test.js @@ -0,0 +1,24 @@ +import React from 'react'; +import Input from './input'; +import { render, screen } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; + +describe('DefaultInput', () => { + test('Should render with props', () => { + const { getByTestId } = render( + , + { wrapper: BrowserRouter } + ); + + expect(screen.getByTestId('default-input')).toBeInTheDocument(); + expect(getByTestId('default-input')).toHaveAttribute('type', 'text'); + expect(getByTestId('default-input')).toHaveAttribute( + 'name', + 'default-input' + ); + expect(getByTestId('default-input')).toHaveAttribute( + 'placeholder', + 'Some text' + ); + }); +}); diff --git a/client-mvp-04/src/components/common/title/title.js b/client-mvp-04/src/components/common/title/title.js new file mode 100644 index 000000000..5c6712ed2 --- /dev/null +++ b/client-mvp-04/src/components/common/title/title.js @@ -0,0 +1,13 @@ +import React from 'react'; +import './title.scss'; + +const Title = () => { + return ( +
+

VRMS

+

Volunteer Relationship Management System

+
+ ); +}; + +export default Title; diff --git a/client-mvp-04/src/components/common/title/title.scss b/client-mvp-04/src/components/common/title/title.scss new file mode 100644 index 000000000..327ca00b4 --- /dev/null +++ b/client-mvp-04/src/components/common/title/title.scss @@ -0,0 +1,39 @@ +@import "../../../sass/variables"; + +.title-container { + margin-left: 35px; + text-align: left; + text-transform: uppercase; + + .title-short { + font-size: 72px; + } + + .title-long { + margin-top: 26px; + line-height: 27px; + font-weight: $font-regular; + } +} + +@media (max-width: 400px) { + .title-container { + .title-long { + margin-top: 18px; + } + + .title-short { + font-size: 68px; + } + } +} + +@media (max-width: 350px) { + .title-container { + margin-left: 25px; + + .title-long { + margin: 15px; + } + } +} diff --git a/client-mvp-04/src/components/common/title/title.test.js b/client-mvp-04/src/components/common/title/title.test.js new file mode 100644 index 000000000..4e8b96316 --- /dev/null +++ b/client-mvp-04/src/components/common/title/title.test.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; +import Title from './title'; + +describe('ProjectTitle', () => { + test('Should render component with name of project', () => { + render(, { wrapper: BrowserRouter }); + expect(screen.getByTestId('title')).toBeInTheDocument(); + const h1 = screen.getByText(/VRMS/i); + const h2 = screen.getByText(/Volunteer Relationship Management System/i); + expect(h1).toBeInTheDocument(); + expect(h2).toBeInTheDocument(); + }); +}); diff --git a/client-mvp-04/src/components/home/home.js b/client-mvp-04/src/components/home/home.js index e2a82c24a..760ac133e 100644 --- a/client-mvp-04/src/components/home/home.js +++ b/client-mvp-04/src/components/home/home.js @@ -2,15 +2,15 @@ import React from 'react'; import './home.scss'; import Button from '../common/button/button'; import RedirectLink from '../common/link/link'; +import Title from '../common/title/title'; const Home = () => { return ( <section data-testid="home" className="home-container"> - <h1 className="home-name">VRMS</h1> - <h2 className="home-title">Volunteer Relationship Management System</h2> + <Title /> <RedirectLink - path={'/page'} + path={'/login'} content={<Button content={`Sign in`} className={`home-button`} />} linkKey={'sign-in-link'} /> diff --git a/client-mvp-04/src/components/home/home.scss b/client-mvp-04/src/components/home/home.scss index 81357f197..5fc4ad8a9 100644 --- a/client-mvp-04/src/components/home/home.scss +++ b/client-mvp-04/src/components/home/home.scss @@ -1,53 +1,42 @@ @import "../../sass/variables"; .home-container { - padding: 40px 90px; - - .home-name { - font-size: 72px; - } - - .home-title { - margin: 26px 0 83px; - line-height: 27px; - font-weight: $font-regular; - text-align: left; - text-transform: uppercase; - } + padding: 40px 60px; .home-button { padding: 6px 40px; + margin-top: 83px; } +} - .home-text { - display: flex; - justify-content: center; - font-size: 15px; - font-weight: $font-bold; +@media (max-height: 896px) { + .home-container { + height: calc(100vh - 160px); } } @media (max-width: 400px) { .home-container { - padding: 40px 70px; - } + padding: 25px 52px; - .home-container .home-title { - margin: 18px 0 40px; + .home-button { + margin-top: 60px; + } } +} - .home-container .home-name { - font-size: 68px; - text-align: left; +@media (max-width: 374px) { + .home-container { + height: calc(100vh - 180px); } } @media (max-width: 350px) { .home-container { - padding: 15px 52px; - } + padding: 15px 25px; - .home-container .home-title { - margin: 15px 0 35px; + .home-button { + margin-top: 35px; + } } } diff --git a/client-mvp-04/src/components/home/home.test.js b/client-mvp-04/src/components/home/home.test.js index e986a1933..92bdd2793 100644 --- a/client-mvp-04/src/components/home/home.test.js +++ b/client-mvp-04/src/components/home/home.test.js @@ -31,9 +31,9 @@ describe('Home', () => { </Router> ); expect(screen.getByText('Sign in')).toBeInTheDocument(); - expect(screen.getAllByTestId('link')[0]).toHaveAttribute('href', '/page'); + expect(screen.getAllByTestId('link')[0]).toHaveAttribute('href', '/login'); fireEvent.click(screen.getAllByTestId('link')[0]); - expect(history.location.pathname).toBe('/page'); + expect(history.location.pathname).toBe('/login'); }); test('Should navigate to dummy page after click on `Create account` button', () => { diff --git a/client-mvp-04/src/components/login/login.scss b/client-mvp-04/src/components/login/login.scss new file mode 100644 index 000000000..96a110c4c --- /dev/null +++ b/client-mvp-04/src/components/login/login.scss @@ -0,0 +1,67 @@ +@import "../../sass/variables"; + +.login-container { + padding: 40px 60px; + + .text-field-container { + margin-top: 76px; + } + + .login-button { + margin: 50px 10px 20px auto; + } + + .error-message { + margin: 25px 10px 10px; + + .redirect-link { + color: $link-accent-color; + } + } +} + +@media (max-height: 896px) { + .login-container { + height: calc(100vh - 160px); + } +} + +@media (max-width: 400px) { + .login-container { + padding: 25px 52px; + + .login-button { + margin: 40px 0 20px auto; + } + + .error-message { + margin: 25px 0 10px; + } + + .text-field-container { + margin-top: 55px; + } + } +} + +@media (max-width: 374px) { + .login-container { + height: calc(100vh - 180px); + } +} + +@media (max-width: 350px) { + .login-container { + padding: 15px 25px; + + .text-field-container { + margin-top: 25px; + } + .login-button { + margin: 15px 0 10px auto; + } + .error-message { + margin: 20px 0 5px; + } + } +} diff --git a/client-mvp-04/src/components/login/loginContainer.js b/client-mvp-04/src/components/login/loginContainer.js new file mode 100644 index 000000000..d5a393091 --- /dev/null +++ b/client-mvp-04/src/components/login/loginContainer.js @@ -0,0 +1,65 @@ +import React, { useState } from 'react'; +import LoginView from './loginView'; +import { connect } from 'react-redux'; +import { Email } from '../../utils/validation'; +import UserService from '../../services/user.service'; +import { loginSuccess } from '../../store/actions/authActions'; +import { setUser, failUser } from '../../store/actions/userActions'; + +const LoginContainer = (props) => { + // Local UI State + const [isDisabled, setIsDisabled] = useState(true); + const [userEmail, setUserEmail] = useState(''); + const [isEmailValid, setIsEmailValid] = useState(false); + const [errorMsgInvalidEmail, setErrorMsgInvalidEmail] = useState(false); + const [errorMsgFailedEmail, setErrorMsgFailedEmail] = useState(false); + + function handleInputChange(e) { + setErrorMsgInvalidEmail(false); + setErrorMsgFailedEmail(false); + const inputValue = e.currentTarget.value.toString(); + inputValue ? setIsDisabled(false) : setIsDisabled(true); + setUserEmail(inputValue); + } + + const handleSubmitForm = async (e) => { + e.preventDefault(); + if (Email.isValid(userEmail)) { + setIsEmailValid(true); + setErrorMsgInvalidEmail(false); + const userData = await UserService.getData(userEmail); + if (userData) { + // user is already registered in app, update global state in store + props.dispatch(loginSuccess()); + props.dispatch(setUser(userData)); + // while functionality isn't implemented redirect to dummy page + props.history.push('/page'); + } else { + setErrorMsgFailedEmail(true); + props.dispatch(failUser()); + } + } else { + setIsEmailValid(false); + setErrorMsgInvalidEmail(true); + } + }; + + return ( + <LoginView + handleSubmitForm={handleSubmitForm} + handleInputChange={handleInputChange} + isDisabled={isDisabled} + isEmailValid={isEmailValid} + errorMsgInvalidEmail={errorMsgInvalidEmail} + errorMsgFailedEmail={errorMsgFailedEmail} + /> + ); +}; + +const mapStateToProps = function (state) { + return { + loggedIn: state.auth.loggedIn, + }; +}; + +export default connect(mapStateToProps)(LoginContainer); diff --git a/client-mvp-04/src/components/login/loginView.js b/client-mvp-04/src/components/login/loginView.js new file mode 100644 index 000000000..294cc1977 --- /dev/null +++ b/client-mvp-04/src/components/login/loginView.js @@ -0,0 +1,57 @@ +import React from 'react'; +import './login.scss'; +import Button from '../common/button/button'; +import Title from '../common/title/title'; +import Input from '../common/input/input'; +import ErrorMessage from '../common/errorMessage/errorMessage'; +import RedirectLink from '../common/link/link'; + +const LoginView = ({ + handleSubmitForm, + handleInputChange, + isDisabled, + isEmailValid, + errorMsgInvalidEmail, + errorMsgFailedEmail, +}) => { + return ( + <section data-testid="login" className="login-container"> + <Title /> + + <form data-testid="login-form" onSubmit={(e) => handleSubmitForm(e)}> + <Input + dataTestid="login-input" + placeholder={'Enter your email'} + type={'email'} + onChange={(e) => handleInputChange(e)} + autoComplete={'email'} + /> + + <Button + type={'submit'} + content={`Sign in`} + className={'login-button'} + disabled={isDisabled} + /> + </form> + + {!isEmailValid && errorMsgInvalidEmail ? ( + <ErrorMessage content={'*Please enter a valid email address'} /> + ) : null} + + {errorMsgFailedEmail ? ( + <p className={'error-message'}> + *We don’t recognize your email address. Need to + <RedirectLink + path={'/page'} + linkKey={'create-acc-link'} + content={' create an account'} + /> + ? + </p> + ) : null} + </section> + ); +}; + +export default LoginView; diff --git a/client-mvp-04/src/components/login/loginView.test.js b/client-mvp-04/src/components/login/loginView.test.js new file mode 100644 index 000000000..7bd1e5126 --- /dev/null +++ b/client-mvp-04/src/components/login/loginView.test.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import LoginView from './loginView'; +import { BrowserRouter } from 'react-router-dom'; + +describe('Login View', () => { + test('Should render component with props', () => { + const props = { + handleSubmitForm: () => {}, + handleInputChange: () => {}, + isDisabled: true, + isEmailValid: false, + errorMsgInvalidEmail: false, + errorMsgFailedEmail: false, + }; + + render( + <LoginView + handleSubmitForm={props.handleSubmitForm} + handleInputChange={props.handleInputChange} + isDisabled={props.isDisabled} + isEmailValid={props.isEmailValid} + errorMsgInvalidEmail={props.errorMsgInvalidEmail} + errorMsgFailedEmail={props.errorMsgFailedEmail} + />, + { wrapper: BrowserRouter } + ); + + expect(screen.getByTestId('login')).toBeInTheDocument(); + expect(screen.getByTestId('login-form')).toBeInTheDocument(); + expect(screen.getByTestId('login-input')).toBeInTheDocument(); + expect(screen.getByText('Sign in')).toBeInTheDocument(); + }); +}); diff --git a/client-mvp-04/src/routes/index.js b/client-mvp-04/src/routes/index.js index 4dc37371a..b7079e61c 100644 --- a/client-mvp-04/src/routes/index.js +++ b/client-mvp-04/src/routes/index.js @@ -1,6 +1,7 @@ import Home from '../components/home/home'; import Dummy from '../components/dummy/dummy'; import Error from '../components/error/error'; +import LoginContainer from '../components/login/loginContainer'; import DevUiKit from '../utils/uiKit/uiKit'; @@ -10,6 +11,11 @@ export const Routes = [ key: 'home', component: Home, }, + { + path: '/login', + key: 'login', + component: LoginContainer, + }, { path: '/page', key: 'dummy', diff --git a/client-mvp-04/src/sass/_main.scss b/client-mvp-04/src/sass/_main.scss index 304e2c556..e82138c52 100644 --- a/client-mvp-04/src/sass/_main.scss +++ b/client-mvp-04/src/sass/_main.scss @@ -18,3 +18,4 @@ @import "elements/_menu"; @import "elements/_inputs"; @import "elements/_tooltip"; +@import "elements/error"; diff --git a/client-mvp-04/src/sass/core/_base.scss b/client-mvp-04/src/sass/core/_base.scss index bbafcb1cb..ce8b03e3e 100644 --- a/client-mvp-04/src/sass/core/_base.scss +++ b/client-mvp-04/src/sass/core/_base.scss @@ -80,6 +80,22 @@ a { font-style: $font-italic; } +/* Change Autocomplete styles in Chrome*/ +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +textarea:-webkit-autofill, +textarea:-webkit-autofill:hover, +textarea:-webkit-autofill:focus, +select:-webkit-autofill, +select:-webkit-autofill:hover, +select:-webkit-autofill:focus { + border: 1px solid $dark-primary-color; + -webkit-text-fill-color: $dark-primary-color; + -webkit-box-shadow: none; + transition: background-color 3000s ease-in-out 0s; +} + @media (max-width: 374px) { h2 { font-size: 22px; diff --git a/client-mvp-04/src/sass/core/_fonts.scss b/client-mvp-04/src/sass/core/_fonts.scss index f5bbe7831..8e20b5646 100644 --- a/client-mvp-04/src/sass/core/_fonts.scss +++ b/client-mvp-04/src/sass/core/_fonts.scss @@ -30,6 +30,24 @@ src: url("../../assets/fonts/open-sans/opensans-italic.woff2") format("woff2"), url("../../assets/fonts/open-sans/opensans-italic.woff") format("woff"), url("../../assets/fonts/open-sans/opensans-italic.ttf") format("ttf"); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Open Sans'; + src: url("../../assets/fonts/open-sans/opensans-italic-semibold.woff2") format("woff2"), + url("../../assets/fonts/open-sans/opensans-italic-semibold.woff") format("woff"), + url("../../assets/fonts/open-sans/opensans-italic-semibold.ttf") format("ttf"); + font-weight: 600; + font-style: italic; +} + +@font-face { + font-family: 'Open Sans'; + src: url("../../assets/fonts/open-sans/opensans-italic-bold.woff2") format("woff2"), + url("../../assets/fonts/open-sans/opensans-italic-bold.woff") format("woff"), + url("../../assets/fonts/open-sans/opensans-italic-bold.ttf") format("ttf"); font-weight: bold; font-style: italic; } diff --git a/client-mvp-04/src/sass/elements/_buttons.scss b/client-mvp-04/src/sass/elements/_buttons.scss index ddd957a0d..52f881ef2 100644 --- a/client-mvp-04/src/sass/elements/_buttons.scss +++ b/client-mvp-04/src/sass/elements/_buttons.scss @@ -15,9 +15,13 @@ button { transition: .3s ease; display: flex; justify-content: center; - &:hover{ + &:hover { background-color: $concrete-color; } + &:disabled { + background-color: $silver-color; + cursor: default; + } } .btn-accent { diff --git a/client-mvp-04/src/sass/elements/_error.scss b/client-mvp-04/src/sass/elements/_error.scss new file mode 100644 index 000000000..3806fb935 --- /dev/null +++ b/client-mvp-04/src/sass/elements/_error.scss @@ -0,0 +1,9 @@ +@import "../variables"; + +.error-message { + color: $accent-burnt-sienna-color; + font-style: $font-italic; + line-height: 25px; + font-weight: $font-semi-bold; + text-align: left; +} diff --git a/client-mvp-04/src/sass/elements/_inputs.scss b/client-mvp-04/src/sass/elements/_inputs.scss index 649f0cc21..c33e9b2c9 100644 --- a/client-mvp-04/src/sass/elements/_inputs.scss +++ b/client-mvp-04/src/sass/elements/_inputs.scss @@ -5,10 +5,10 @@ justify-content: center; margin: 0 auto; max-width: 246px; - width: 100%; + width: 246px; input { - width: 100%; + width: 246px; font-size: 16px; line-height: 20px; color: $dark-primary-color; diff --git a/client-mvp-04/src/services/user.service.js b/client-mvp-04/src/services/user.service.js new file mode 100644 index 000000000..764fb23ea --- /dev/null +++ b/client-mvp-04/src/services/user.service.js @@ -0,0 +1,21 @@ +const headers = { + 'Content-Type': 'application/json', + 'x-customrequired-header': process.env.REACT_APP_CUSTOM_REQUEST_HEADER, +}; + +const UserService = { + async getData(email) { + try { + const response = await fetch('/api/checkuser', { + method: 'POST', + headers: headers, + body: JSON.stringify({ email: email }), + }); + return await response.json(); + } catch (error) { + console.log(error); + } + }, +}; + +export default UserService; diff --git a/client-mvp-04/src/setupProxy.js b/client-mvp-04/src/setupProxy.js new file mode 100644 index 000000000..afb4abc15 --- /dev/null +++ b/client-mvp-04/src/setupProxy.js @@ -0,0 +1,13 @@ +// setupProxy.js - Dynamically setup a proxy from the frontend to the backend. This file +// does not need to be imported. https://create-react-app.dev/docs/adding-custom-environment-variables/ + +const { createProxyMiddleware } = require("http-proxy-middleware"); +module.exports = function (app) { + app.use( + "/api", + createProxyMiddleware({ + target: process.env.REACT_APP_PROXY, + changeOrigin: true, + }) + ); +}; diff --git a/client-mvp-04/src/store/actions/authActions.js b/client-mvp-04/src/store/actions/authActions.js index fa5e6c3ac..66aafd305 100644 --- a/client-mvp-04/src/store/actions/authActions.js +++ b/client-mvp-04/src/store/actions/authActions.js @@ -1,3 +1,4 @@ -import { LOGIN } from './types'; +import { LOGIN_SUCCESS, LOGIN_FAIL } from './types'; -export const login = () => ( {type : LOGIN} ); +export const loginSuccess = () => ({ type: LOGIN_SUCCESS }); +export const loginFailed = () => ({ type: LOGIN_FAIL }); diff --git a/client-mvp-04/src/store/actions/types.js b/client-mvp-04/src/store/actions/types.js index 5f665d90b..04d1d515b 100644 --- a/client-mvp-04/src/store/actions/types.js +++ b/client-mvp-04/src/store/actions/types.js @@ -1,2 +1,10 @@ -// Auth -export const LOGIN = "LOGIN"; +// AUTH +export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; +export const LOGIN_FAIL = 'LOGIN_FAIL'; + +// USER +export const SET_USER = 'SET_USER'; +export const FAIL_USER = 'FAIL_USER'; + +// Reset State +export const RESET_STATE = 'RESET_STATE'; diff --git a/client-mvp-04/src/store/actions/userActions.js b/client-mvp-04/src/store/actions/userActions.js new file mode 100644 index 000000000..b1fc0fcb4 --- /dev/null +++ b/client-mvp-04/src/store/actions/userActions.js @@ -0,0 +1,4 @@ +import { SET_USER, FAIL_USER } from './types'; + +export const setUser = (user) => ({ type: SET_USER, payload: user }); +export const failUser = () => ({ type: FAIL_USER }); diff --git a/client-mvp-04/src/store/reducers/authReducer.js b/client-mvp-04/src/store/reducers/authReducer.js index 6b0d64ff7..9b4e7b286 100644 --- a/client-mvp-04/src/store/reducers/authReducer.js +++ b/client-mvp-04/src/store/reducers/authReducer.js @@ -1,16 +1,21 @@ -import { LOGIN } from '../actions/types'; +import { LOGIN_SUCCESS, LOGIN_FAIL } from '../actions/types'; const authDefaultState = { loggedIn: false, }; -export default (state = authDefaultState, { type, payload }) => { +export default (state = authDefaultState, { type }) => { switch (type) { - case LOGIN: + case LOGIN_SUCCESS: return { ...state, loggedIn: true, }; + case LOGIN_FAIL: + return { + ...state, + loggedIn: false, + }; default: return state; } diff --git a/client-mvp-04/src/store/reducers/index.js b/client-mvp-04/src/store/reducers/index.js index 72fe771f4..f0aee9238 100644 --- a/client-mvp-04/src/store/reducers/index.js +++ b/client-mvp-04/src/store/reducers/index.js @@ -1,8 +1,10 @@ -import authReducer from './authReducer'; import { combineReducers } from 'redux'; +import authReducer from './authReducer'; +import userReducer from './userReducer'; const allReducers = combineReducers({ - auth: authReducer -}) + auth: authReducer, + user: userReducer, +}); export default allReducers; diff --git a/client-mvp-04/src/store/reducers/userReducer.js b/client-mvp-04/src/store/reducers/userReducer.js new file mode 100644 index 000000000..11853c8c3 --- /dev/null +++ b/client-mvp-04/src/store/reducers/userReducer.js @@ -0,0 +1,22 @@ +import { SET_USER, FAIL_USER } from '../actions/types'; + +const userInitialState = { + user: null, +}; + +export default (state = userInitialState, { type, payload }) => { + switch (type) { + case SET_USER: + return { + ...state, + user: payload, + }; + case FAIL_USER: + return { + ...state, + user: null, + }; + default: + return state; + } +}; diff --git a/client-mvp-04/src/utils/uiKit/uiKit.js b/client-mvp-04/src/utils/uiKit/uiKit.js index 4f744dcde..9833eec2b 100644 --- a/client-mvp-04/src/utils/uiKit/uiKit.js +++ b/client-mvp-04/src/utils/uiKit/uiKit.js @@ -6,6 +6,8 @@ import homeIcon from '../../assets/images/icons/home.png'; import projectIcon from '../../assets/images/icons/311.png'; import gitHubIcon from '../../assets/images/icons/github.png'; import RedirectLink from '../../components/common/link/link'; +import Input from '../../components/common/input/input'; +import ErrorMessage from '../../components/common/errorMessage/errorMessage'; /***** DEV-UI-KIT FOR DEVELOPMENT ONLY *****/ /*UI KIT helps devs determine, which UI elements will be used throughout @@ -91,6 +93,11 @@ const DevUiKit = () => { <span className={'project-link-name'}>311 Data Project</span> </div> <p className={'dev-comment'}>Project Link: 24px, semi-bold</p> + + <ErrorMessage content={'*Please enter a valid email address'} /> + <p className={'dev-comment'}> + Error Message: italic, semi-bold; components/common/errorMessage + </p> </div> {/*** BUTTONS ***/} @@ -150,14 +157,9 @@ const DevUiKit = () => { {/*** INPUTS ***/} <div className={'inputs-container'}> <h1 className={'kit-sec-title'}>*** INPUTS ***</h1> - <div className={'text-field-container'}> - <input - type="text" - name="text-field" - placeholder={'Enter your email'} - /> - </div> - <p className={'dev-comment'}>Default Input</p> + + <Input placeholder={'Enter your email'} type={'email'} /> + <p className={'dev-comment'}>Default Input, components/common/input</p> </div> <div className={'text-field-icon-container'}> diff --git a/client-mvp-04/src/utils/uiKit/uiKit.scss b/client-mvp-04/src/utils/uiKit/uiKit.scss index 657d7ada6..3fbf4f70a 100644 --- a/client-mvp-04/src/utils/uiKit/uiKit.scss +++ b/client-mvp-04/src/utils/uiKit/uiKit.scss @@ -26,6 +26,6 @@ border-radius: 0 0 6px 6px; border-top: 2px solid darkgray; justify-content: center; - margin: 5px 5px 25px; + margin: 5px 0 25px; } } diff --git a/client-mvp-04/src/utils/validation.js b/client-mvp-04/src/utils/validation.js new file mode 100644 index 000000000..4fa81e111 --- /dev/null +++ b/client-mvp-04/src/utils/validation.js @@ -0,0 +1,6 @@ +export class Email { + static isValid(value) { + const pattern = /\b[a-z0-9._]+@[a-z0-9.-]+\.[a-z]{2,4}\b/i; + return value.search(pattern) !== -1; + } +}