Entony 5 vuotta sitten
vanhempi
commit
3a896af27a

+ 4 - 0
src/actionTypes/actionTypes.js

@@ -36,3 +36,7 @@ export const REMOVE_REQUEST_FAIL = "REMOVE_REQUEST_FAIL";
 export const UPDATE_REQUEST = "UPDATE_REQUEST";
 export const UPDATE_REQUEST_SUCCESS = "UPDATE_REQUEST_SUCCESS";
 export const UPDATE_REQUEST_FAIL = "UPDATE_REQUEST_FAIL";
+
+export const AUTH_REQUEST = "AUTH_REQUEST";
+export const AUTH_REQUEST_SUCCESS = "AUTH_REQUEST_SUCCESS";
+export const AUTH_REQUEST_FAIL = "AUTH_REQUEST_FAIL";

+ 29 - 0
src/actions/auth.js

@@ -0,0 +1,29 @@
+import * as types from "../actionTypes/actionTypes";
+
+import axios from "axios";
+
+const authRequest = payload => ({
+	type: types.AUTH_REQUEST,
+	payload
+});
+const authRequestSuccess = payload => ({
+	type: types.AUTH_REQUEST_SUCCESS,
+	payload
+});
+const authRequestFail = payload => ({
+	type: types.AUTH_REQUEST_FAIL,
+	payload
+});
+
+export const auth = payload => {
+	return async dispatch => {
+		dispatch(authRequest());
+		try {
+			const { data } = await axios.post("http://subdomain.entony.fs.a-level.com.ua/api/auth/login", payload);
+
+			dispatch(authRequestSuccess(data));
+		} catch (error) {
+			dispatch(authRequestFail(error));
+		}
+	};
+};

+ 117 - 0
src/components/auth/auth.js

@@ -0,0 +1,117 @@
+import React, { Component } from "react";
+
+import Input from "../input";
+import Button from "../button/button";
+
+export default class AuthForm extends Component {
+	state = {
+		form: {
+			email: {
+				id: 1,
+				name: "email",
+				type: "email",
+				label: "Email",
+				validation: {
+					requred: true
+				},
+				fail: false,
+				touch: false,
+				value: ""
+			},
+			password: {
+				id: 2,
+				name: "password",
+				type: "password",
+				label: "Password",
+				validation: {
+					requred: true,
+					minL: 6
+				},
+				fail: false,
+				touch: false,
+				value: ""
+			}
+		},
+		validForm: false
+	};
+
+	validator = (rules, value) => {
+		const { requred, minL } = rules;
+
+		let valid = true;
+		if (requred) {
+			valid = value.trim() === "" && valid;
+		}
+
+		if (minL) {
+			valid = value.trim().length < minL && valid;
+		}
+
+		return valid;
+	};
+
+	submit = e => {
+		e.preventDefault();
+
+		const values = Object.keys(this.state.form).reduce((prev, elem) => {
+			return { ...prev, [elem]: this.state.form[elem].value };
+		}, {});
+
+		this.props.submitHandler(values);
+	};
+
+	onChangeHandler = e => {
+		const { name, value } = e.target;
+
+		this.setState(prevState => {
+			const values = Object.keys(prevState.form).reduce((prev, elem) => {
+				if (elem === name) return prev.concat(value);
+				return prev.concat(prevState.form[elem].value);
+			}, []);
+
+			return {
+				...prevState,
+				form: {
+					...prevState.form,
+					[name]: {
+						...prevState.form[name],
+						value,
+						touch: true,
+						fail: this.validator(prevState.form[name].validation, value)
+					}
+				},
+				validForm: values.some(value => value)
+			};
+		});
+	};
+
+	render() {
+		const { form, validForm } = this.state;
+		const { error } = this.props;
+		return (
+			<div className="auth__auth-box">
+				<form onSubmit={this.submit} className="auth__auth-form">
+					{Object.keys(form).map(input_name => {
+						return (
+							<Input
+								key={form[input_name].id}
+								id={form[input_name].id}
+								value={form[input_name].value}
+								name={form[input_name].name}
+								type={form[input_name].type}
+								fail={form[input_name].fail}
+								touch={form[input_name].touch}
+								label={form[input_name].label}
+								onChange={this.onChangeHandler}
+							/>
+						);
+					})}
+					{error && <p className="auth__error-auth-text">{error}</p>}
+					<div className="auth__control-box">
+						<Button disabled={!validForm} className="auth__submit-btn" type="submit" text="Log In" />
+					</div>
+				</form>
+			</div>
+		);
+	}
+}

+ 7 - 0
src/components/auth/register.js

@@ -0,0 +1,7 @@
+import React, { Component } from "react";
+
+export default class RegisterForm extends Component {
+	render() {
+		return <div>RegisterForm</div>;
+	}
+}

+ 18 - 10
src/components/header/index.js

@@ -1,11 +1,13 @@
 import React from "react";
 import { Link, withRouter } from "react-router-dom";
+import { connect } from "react-redux";
 
 const liArr = [
-	{ path: "/", id: 1, text: "Main" },
-	{ path: "/count", id: 2, text: "Counter from redux" },
-	{ path: "/redux-todo", id: 3, text: "Todo redux" },
-	{ path: "/remote-todo", id: 4, text: "Remote todo" }
+	{ path: "/", id: 1, text: "Main", hideWhenAuth: false },
+	{ path: "/count", id: 2, text: "Counter from redux", hideWhenAuth: false },
+	{ path: "/redux-todo", id: 3, text: "Todo redux", hideWhenAuth: false },
+	{ path: "/remote-todo", id: 4, text: "Remote todo", hideWhenAuth: false },
+	{ path: "/auth", id: 5, text: "Auth", hideWhenAuth: true }
 ];
 
 const header = props => {
@@ -22,11 +24,13 @@ const header = props => {
 				<div className="header__logo-box">logo</div>
 				<nav className="header__nav">
 					<ul className="header__list">
-						{liArr.map(el => (
-							<li className="header__item" key={el.id}>
-								<Link to={el.path}>{el.text}</Link>
-							</li>
-						))}
+						{liArr.map(el =>
+							el.hideWhenAuth && props.user ? null : (
+								<li className="header__item" key={el.id}>
+									<Link to={el.path}>{el.text}</Link>
+								</li>
+							)
+						)}
 					</ul>
 				</nav>
 				<button onClick={event}>Click and wait 5s</button>
@@ -35,4 +39,8 @@ const header = props => {
 	);
 };
 
-export default withRouter(header);
+const mapStateToProps = state => ({
+	user: state.auth.user
+});
+
+export default connect(mapStateToProps)(withRouter(header));

+ 7 - 2
src/components/input/index.js

@@ -2,9 +2,14 @@ import React from "react";
 
 import "./index.scss";
 
-export default ({ id, label, type = "text", ...rest }) => (
+export default ({ id, label, type = "text", fail, touch, ...rest }) => (
 	<label htmlFor={id} className="input-box">
 		{label}
-		<input className="input-box__input" id={id} type={type} {...rest} />
+		<input
+			className={fail && touch ? "input-box__input input-box__input--fail" : "input-box__input"}
+			id={id}
+			type={type}
+			{...rest}
+		/>
 	</label>
 );

+ 21 - 0
src/components/input/index.scss

@@ -3,5 +3,26 @@
 	flex-direction: column;
 
 	&__input {
+		font-family: inherit;
+		color: inherit;
+		font-size: 1.5rem;
+		padding: 0.5rem 1rem;
+		border-radius: 2px;
+		background-color: rgba(255, 255, 255, 0.5);
+		border: none;
+		border-bottom: 3px solid #eee;
+		width: 100%;
+		display: block;
+		transition: all 0.3s;
+
+		&:focus {
+			outline: none;
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.1);
+			border-bottom: 3px solid green;
+		}
+
+		&--fail {
+			border-bottom: 3px solid #9e4560;
+		}
 	}
 }

+ 66 - 0
src/containers/auth.js

@@ -0,0 +1,66 @@
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import { Redirect } from "react-router-dom";
+
+import { auth } from "../actions/auth";
+
+import RegisterForm from "../components/auth/register";
+import AuthForm from "../components/auth/auth";
+import Loader from "../components/HOC/loader";
+
+export class AuthContainer extends Component {
+	state = { auth: true };
+
+	toggleAuth = () => this.setState(prevState => ({ auth: !prevState.auth }));
+
+	render() {
+		const { auth } = this.state;
+
+		if (this.props.user) {
+			return <Redirect to="/" />;
+		}
+
+		return (
+			<div className="auth">
+				<div className="auth__content">
+					{auth ? (
+						<Loader flag={this.props.isFetching}>
+							<AuthForm error={this.props.errorFromAuth} submitHandler={this.props.auth} />
+						</Loader>
+					) : (
+						<RegisterForm />
+					)}
+
+					<div className="auth__addintional-content">
+						{auth ? (
+							<p className="auth__text">
+								Do you have account ?{" "}
+								<span className="auth__toggle-span" onClick={this.toggleAuth}>
+									Sing Up
+								</span>
+							</p>
+						) : (
+							<p className="auth__text">
+								I have account{" "}
+								<span className="auth__toggle-span" onClick={this.toggleAuth}>
+									Sign In
+								</span>
+							</p>
+						)}
+					</div>
+				</div>
+			</div>
+		);
+	}
+}
+
+const mapStateToProps = state => ({
+	user: state.auth.user,
+	isFetching: state.auth.isFetching,
+	errorFromAuth: state.auth.error
+});
+
+export default connect(
+	mapStateToProps,
+	{ auth }
+)(AuthContainer);

+ 1 - 3
src/index.js

@@ -3,18 +3,16 @@ import ReactDOM from "react-dom";
 import { BrowserRouter } from "react-router-dom";
 import { Provider } from "react-redux";
 
-import Header from "./components/header";
 import App from "./router";
 import * as serviceWorker from "./serviceWorker";
 
-import "./style.scss";
+import "./styles/index.scss";
 
 import { store } from "./store";
 
 ReactDOM.render(
 	<Provider store={store}>
 		<BrowserRouter>
-			<Header />
 			<App />
 		</BrowserRouter>
 	</Provider>,

+ 28 - 0
src/reducer/auth.js

@@ -0,0 +1,28 @@
+import * as types from "../actionTypes/actionTypes";
+
+const initialState = {
+	user: null,
+	isFetching: false,
+	token: null,
+	error: null
+};
+
+export default (state = initialState, action) => {
+	switch (action.type) {
+		case types.AUTH_REQUEST: {
+			return { ...state, isFetching: true };
+		}
+
+		case types.AUTH_REQUEST_SUCCESS: {
+			const { user, token } = action.payload;
+			return { ...state, isFetching: false, user, token };
+		}
+
+		case types.AUTH_REQUEST_FAIL: {
+			return { ...state, isFetching: false, error: action.payload.response.data.message };
+		}
+
+		default:
+			return state;
+	}
+};

+ 3 - 1
src/reducer/index.js

@@ -4,10 +4,12 @@ import counter from "./todo";
 import request from "./request";
 import redux_todo from "./redux-todo";
 import remote_todo from "./remote-todo";
+import auth from "./auth";
 
 export default combineReducers({
 	counter,
 	request,
 	redux_todo,
-	remote_todo
+	remote_todo,
+	auth
 });

+ 24 - 17
src/router.js

@@ -1,29 +1,36 @@
 import React from "react";
 import { Switch, Route, Redirect } from "react-router-dom";
 
+import Header from "./components/header";
+
 import Main from "./containers/main";
 import Count from "./containers/count";
 import ReduxTodo from "./containers/reduxTodo";
 import RemoteTodo from "./containers/remote-todo";
+import AuthContainer from "./containers/auth";
 
 export default () => (
-	<Switch>
-		<Route exact path="/" component={Main} />
-		<Route exact path="/count" component={Count} />
-		<Route exact path="/todo" component={Count} />
-		<Route exact path="/redux-todo" component={ReduxTodo} />
-		<Route exact path="/remote-todo" component={RemoteTodo} />
+	<div className="container">
+		<Header />
+		<Switch>
+			<Route exact path="/" component={Main} />
+			<Route exact path="/count" component={Count} />
+			<Route exact path="/todo" component={Count} />
+			<Route exact path="/redux-todo" component={ReduxTodo} />
+			<Route exact path="/remote-todo" component={RemoteTodo} />
+			<Route exact path="/auth" component={AuthContainer} />
 
-		<Route
-			exact
-			path="/exact"
-			render={() => (
-				<div>
-					<Redirect to="/" />
-				</div>
-			)}
-		/>
+			<Route
+				exact
+				path="/exact"
+				render={() => (
+					<div>
+						<Redirect to="/" />
+					</div>
+				)}
+			/>
 
-		<Route exact render={() => <div>PAGE 404 NOT FOUND</div>} />
-	</Switch>
+			<Route exact render={() => <div>PAGE 404 NOT FOUND</div>} />
+		</Switch>
+	</div>
 );

+ 109 - 0
src/styles/base/_base.scss

@@ -0,0 +1,109 @@
+*,
+*:after,
+*:before {
+	margin: 0;
+	padding: 0;
+	-webkit-box-sizing: inherit;
+	box-sizing: inherit;
+}
+
+html {
+	font-size: 62.5% !important;
+
+	// @include breakpoint(lg) {
+	// 	font-size: 56.25% !important;
+	// }
+	// @include breakpoint(md) {
+	// 	font-size: 50% !important;
+	// }
+	// @include breakpoint(sm) {
+	// 	font-size: 62.5% !important;
+	// }
+}
+
+body,
+div,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+pre,
+form,
+fieldset,
+input,
+textarea,
+p,
+blockquote,
+th,
+td {
+	padding: 0;
+	margin: 0;
+}
+textarea {
+	resize: none;
+}
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+fieldset,
+img {
+	border: 0;
+}
+address,
+caption,
+cite,
+code,
+dfn,
+em,
+strong,
+th,
+var {
+	font-weight: normal;
+	font-style: normal;
+}
+ol,
+ul {
+	list-style: none;
+}
+caption,
+th {
+	text-align: left;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+	font-weight: normal;
+}
+
+abbr,
+acronym {
+	border: 0;
+}
+
+body {
+	font-family: "Raleway", sans-serif !important;
+	line-height: 1.7 !important;
+	font-size: inherit !important;
+	box-sizing: border-box;
+	min-width: 320px;
+}
+
+a:hover {
+	text-decoration: none;
+}
+
+button {
+	cursor: pointer;
+}

+ 3 - 7
src/style.scss

@@ -1,10 +1,6 @@
-body {
-	margin: 0;
-}
-
-.container {
-	width: 100vw;
-}
+@import "./base/base";
+@import "./layout/layout";
+@import "./pages/auth";
 
 .header {
 	width: 100%;

+ 4 - 0
src/styles/layout/_layout.scss

@@ -0,0 +1,4 @@
+.container {
+	width: 100vw;
+	height: 100vh;
+}

+ 78 - 0
src/styles/pages/_auth.scss

@@ -0,0 +1,78 @@
+.auth {
+	width: 100%;
+	height: 100%;
+
+	position: relative;
+
+	&__content {
+		width: 30rem;
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+
+		padding: 2rem;
+		border: 1px solid #eee;
+	}
+
+	&__text {
+		font-size: 1.6rem;
+	}
+
+	&__toggle-span {
+		font-size: 1.6rem;
+		font-weight: bold;
+		color: rgb(102, 104, 223);
+	}
+
+	&__auth-box {
+		margin-bottom: 2rem;
+	}
+
+	&__control-box {
+		margin-top: 2rem;
+	}
+
+	&__error-auth-text {
+		margin-top: 1rem;
+		text-align: center;
+		width: 100%;
+		color: #8f240a;
+		font-size: 2rem;
+	}
+
+	&__submit-btn {
+		width: 100%;
+		border: 1px solid #eee;
+		border-radius: 3px;
+
+		color: #fff;
+		background-color: #5f6df0;
+		border: none;
+		cursor: pointer;
+		font-weight: 600;
+		font-size: 1.2rem;
+		transition: 0.2s;
+		padding: 0.5rem 1rem;
+
+		&:focus {
+			outline: none;
+		}
+		&:hover {
+			color: #5f6df0;
+			background-color: #fff;
+			outline: none;
+			transform: translateY(-1px);
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.2);
+			&:after {
+				transform: scaleX(1.4) scaleY(1.6);
+				opacity: 0;
+			}
+		}
+
+		&:disabled {
+			color: #fff;
+			background-color: #eee;
+		}
+	}
+}