Commit 50b7e654 authored by zarina's avatar zarina 🌊

#2, реализовала регистрацию и аутентификацию пользователей

parent 36808a83
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -3,42 +3,32 @@ ...@@ -3,42 +3,32 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^4.2.4", "axios": "^0.18.0",
"@testing-library/react": "^9.5.0", "bootstrap": "^4.3.1",
"@testing-library/user-event": "^7.2.1", "connected-react-router": "^6.8.0",
"axios": "^0.19.2", "history": "^4.10.1",
"bootstrap": "^4.5.0", "prop-types": "^15.7.2",
"history": "^5.0.0", "react": "^16.8.4",
"prop-type": "0.0.1", "react-dom": "^16.8.4",
"react": "^16.13.1", "react-notifications": "^1.6.0",
"react-dom": "^16.13.1", "react-redux": "^6.0.1",
"react-notification": "^6.8.5", "react-router": "^5.2.0",
"react-redux": "^7.2.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1", "react-scripts": "^2.1.8",
"reactstrap": "^8.5.1", "reactstrap": "^7.1.0",
"redux": "^4.0.5", "redux": "^4.0.1",
"redux-thunk": "^2.3.0" "redux-thunk": "^2.3.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "browserslist": [
"extends": "react-app" ">0.2%",
}, "not dead",
"browserslist": { "not ie <= 11",
"production": [ "not op_mini all"
">0.2%", ]
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
} }
import React from 'react'; import React, {Component, Fragment} from 'react';
import logo from './logo.svg'; import Toolbar from "./components/UI/Toolbar/Toolbar";
import './App.css'; import {Container} from "reactstrap";
import {Route, Switch, withRouter} from "react-router-dom";
import Register from "./containers/Register/Register";
import Login from "./containers/Login/Login";
import {connect} from "react-redux";
import {NotificationContainer} from "react-notifications";
import {logoutUser} from "./store/actions/usersActions";
function App() { class App extends Component {
return ( render() {
<div className="App"> return (
<header className="App-header"> <Fragment>
<img src={logo} className="App-logo" alt="logo" /> <NotificationContainer />
<p> <header>
Edit <code>src/App.js</code> and save to reload. <Toolbar
</p> user={this.props.user}
<a logout={this.props.logoutUser}
className="App-link" />
href="https://reactjs.org" </header>
target="_blank" <main>
rel="noopener noreferrer" <Container className="mt-5">
> <Switch>
Learn React <Route path="/register" exact component={Register} />
</a> <Route path="/login" exact component={Login} />
</header> </Switch>
</div> </Container>
); </main>
</Fragment>
);
}
} }
export default App; const mapStateToProps = state => {
return {
user: state.users.user
}
};
const mapDispatchToProps = dispatch => {
return {
logoutUser: () => dispatch(logoutUser())
}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
\ No newline at end of file
import axios from "axios";
const instance = axios.create({
baseURL: "http://localhost:8000"
});
export default instance;
\ No newline at end of file
import React from "react";
import PropTypes from "prop-types";
import {Col, FormFeedback, FormGroup, Input, Label} from "reactstrap";
const FormElement = props => {
return (
<FormGroup row>
<Label sm={2} for={props.propertyName}>{props.label}</Label>
<Col sm={10}>
<Input
type={props.type}
required={props.required}
name={props.propertyName}
id={props.propertyName}
value={props.value}
onChange={props.onChange}
invalid={!!props.error}
placeholder={props.placeholder}
min={props.min}
/>
{
props.error &&
<FormFeedback>
{props.error}
</FormFeedback>
}
</Col>
</FormGroup>
);
};
FormElement.propTypes = {
propertyName: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
required: PropTypes.bool,
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
error: PropTypes.string,
type: PropTypes.string
};
export default FormElement;
import React, {Fragment} from 'react';
import {NavItem, NavLink} from "reactstrap";
import {NavLink as RouterNavLink} from "react-router-dom";
const Menu = () => (
<Fragment>
<NavItem>
<NavLink tag={RouterNavLink} to="/" exact>
Products
</NavLink>
</NavItem>
<NavItem className='ml-5'>
<NavLink tag={RouterNavLink} to="/register" exact>
Sign Up
</NavLink>
</NavItem>
<NavItem>
<NavLink tag={RouterNavLink} to="/login" exact>
Login
</NavLink>
</NavItem>
</Fragment>
);
export default Menu;
import React from 'react';
import {DropdownItem, DropdownMenu, DropdownToggle, NavItem, NavLink, UncontrolledDropdown} from "reactstrap";
import {NavLink as RouterNavLink} from "react-router-dom";
const UserMenu = ({user, logout}) => {
return(
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>
Hello, {user.displayName}!
</DropdownToggle>
<NavItem>
<NavLink tag={RouterNavLink} to="/products" exact>
Products
</NavLink>
</NavItem>
<DropdownMenu right>
<DropdownItem>
<NavLink tag={RouterNavLink} to="/products/new" exact>Create product</NavLink>
</DropdownItem>
<DropdownItem divider />
<DropdownItem
onClick={logout}>
Logout
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
)};
export default UserMenu;
import React from "react";
import {NavLink as RouterNavLink} from "react-router-dom";
import {
Container,
Nav,
Navbar,
NavbarBrand,
} from "reactstrap";
import Menu from "./Menus/Menu";
import UserMenu from "./Menus/UserMenu";
const Toolbar = ({user, logout}) => {
return (
<Navbar color="dark" dark expand="md">
<Container>
<NavbarBrand tag={RouterNavLink} to="/">Market</NavbarBrand>
<Nav className="ml-auto" navbar>
{
user ?
<UserMenu user={user} logout={logout} />
:
<>
<Menu />
</>
}
</Nav>
</Container>
</Navbar>
);
};
export default Toolbar;
export default {
apiURL: "http://localhost:8000"
}
import React, {Component} from "react";
import {Alert, Button, Col, Form, FormGroup} from "reactstrap";
import {connect} from "react-redux";
import FormElement from "../../components/UI/FormElement/FormElement";
import {loginUser} from "../../store/actions/usersActions";
class Login extends Component {
state = {
username: "",
password: ""
};
inputChangeHandler = (e) => {
this.setState({[e.target.name]: e.target.value});
};
formSubmitHandler = (e) => {
e.preventDefault();
this.props.onLoginUser({...this.state});
};
render() {
return (
<>
<h2>Login</h2>
{
this.props.error &&
<Alert color="danger">{this.props.error.error}</Alert>
}
<Form onSubmit={this.formSubmitHandler}>
<FormElement
propertyName="username"
label="Username"
onChange={this.inputChangeHandler}
value={this.state.username}
required={false}
type="text"
/>
<FormElement
propertyName="password"
label="Password"
onChange={this.inputChangeHandler}
value={this.state.password}
required={false}
type="password"
/>
<FormGroup row>
<Col sm={{offset:2, size: 10}}>
<Button type="submit" color="primary">
Login
</Button>
</Col>
</FormGroup>
</Form>
</>
);
}
}
const mapStateToProps = state => {
return {
error: state.users.loginError
};
};
const mapDispatchToProps = dispatch => {
return {
onLoginUser: userData => dispatch(loginUser(userData))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
import React, {Component} from "react";
import {Button, Col, Form, FormGroup} from "reactstrap";
import {connect} from "react-redux";
import FormElement from "../../components/UI/FormElement/FormElement";
import {registerUser} from "../../store/actions/usersActions";
class Register extends Component {
state = {
username: "",
password: "",
displayName: "",
phone: ""
};
inputChangeHandler = (e) => {
this.setState({[e.target.name]: e.target.value});
};
formSubmitHandler = (e) => {
e.preventDefault();
this.props.onRegisterUser({...this.state});
};
getFieldError = fieldName => {
return this.props.error && this.props.error.errors && this.props.error.errors[fieldName] && this.props.error.errors[fieldName].message;
};
render() {
return (
<>
<h2>Register new user</h2>
<Form onSubmit={this.formSubmitHandler}>
<FormElement
propertyName="username"
label="Username"
onChange={this.inputChangeHandler}
value={this.state.username}
required={true}
type="text"
error={this.getFieldError("username")}
/>
<FormElement
propertyName="password"
label="Password"
onChange={this.inputChangeHandler}
value={this.state.password}
required={true}
type="password"
error={this.getFieldError("password")}
/>
<FormElement
propertyName="displayName"
label="Display name"
onChange={this.inputChangeHandler}
value={this.state.displayName}
required={false}
type="text"
error={this.getFieldError("displayName")}
/>
<FormElement
propertyName="phone"
label="Phone number"
onChange={this.inputChangeHandler}
value={this.state.phone}
required={false}
type="text"
error={this.getFieldError("phone")}
/>
<FormGroup row>
<Col sm={{offset:2, size: 10}}>
<Button type="submit" color="primary">
Register
</Button>
</Col>
</FormGroup>
</Form>
</>
);
}
}
const mapStateToProps = state => {
return {
error: state.users.registerError
};
};
const mapDispatchToProps = dispatch => {
return {
onRegisterUser: userData => dispatch(registerUser(userData))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Register);
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './index.css'; import {Provider} from 'react-redux';
import App from './App';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
import "react-notifications/lib/notifications.css"
import 'bootstrap/dist/css/bootstrap.min.css';
import App from './App';
import {store, history} from "./store/configureStore";
import {ConnectedRouter} from "connected-react-router";
ReactDOM.render(
<React.StrictMode> const app = (
<App /> <Provider store={store}>
</React.StrictMode>, <ConnectedRouter history={history}>
document.getElementById('root') <App/>
</ConnectedRouter>
</Provider>
); );
// If you want your app to work offline and load faster, you can change ReactDOM.render(app, document.getElementById('root'));
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister(); serviceWorker.unregister();
export const REGISTER_USER_SUCCESS = "REGISTER_USER_SUCCESS";
export const REGISTER_USER_FAILURE = "REGISTER_USER_FAILURE";
export const LOGIN_USER_SUCCESS = "LOGIN_USER_SUCCESS";
export const LOGIN_USER_FAILURE = "LOGIN_USER_FAILURE";
export const LOGOUT_USER = "LOGOUT_USER";
export const FETCH_ERROR = 'FETCH_ERROR';
\ No newline at end of file
import {
LOGIN_USER_FAILURE,
LOGIN_USER_SUCCESS,
LOGOUT_USER,
REGISTER_USER_FAILURE,
REGISTER_USER_SUCCESS
} from "../actionTypes";
import axios from "../../axios-api";
import {push} from "connected-react-router";
import {NotificationManager} from "react-notifications";
const registerUserSuccess = () => {
return {type: REGISTER_USER_SUCCESS};
};
const registerUserFailure = (error) => {
return {type: REGISTER_USER_FAILURE, error};
};
const loginUserSuccess = user => {
return {type: LOGIN_USER_SUCCESS, user};
};
const loginUserFailure = error => {
return {type: LOGIN_USER_FAILURE, error};
};
export const registerUser = (userData) => {
return (dispatch) => {
return axios.post("/users", userData).then(() => {
dispatch(registerUserSuccess());
dispatch(push("/"));
}, error => {
if (error.response && error.response.data) {
dispatch(registerUserFailure(error.response.data));
} else {
dispatch(registerUserFailure({global: "No internet"}));
}
});
};
};
export const loginUser = userData => {
return dispatch => {
axios.post("/users/sessions", userData).then(response => {
dispatch(loginUserSuccess(response.data));
dispatch(push("/"));
}, error => {
dispatch(loginUserFailure(error.response.data));
});
};
};
export const logoutUser = () => {
return (dispatch, getState) => {
const token = getState().users.user.token;
const headers = {Token: token};
axios.delete('/users/sessions', {headers})
.then(() => {
dispatch({type: LOGOUT_USER});
dispatch(push('/'));
NotificationManager.success("Logout success");
});
};
};
\ No newline at end of file
import {createBrowserHistory} from "history";
import {connectRouter, routerMiddleware} from "connected-react-router";
import {loadFromLocalStorage, saveToLocalStorage} from "./localStorage";
import {applyMiddleware, combineReducers, compose, createStore} from "redux";
import thunkMiddleware from 'redux-thunk';
import usersReducer from "./reducers/usersReducer";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export const history = createBrowserHistory();
const rootReducer = combineReducers({
users: usersReducer,
router: connectRouter(history)
});
const middleware = [
thunkMiddleware,
routerMiddleware(history)
];
const enhancers = composeEnhancers(applyMiddleware(...middleware));
const persistedState = loadFromLocalStorage();
export const store = createStore(rootReducer, persistedState, enhancers);
store.subscribe(() => {
saveToLocalStorage({
users: {
user: store.getState().users.user
}
})
});
export const saveToLocalStorage = state => {
try {
const serializedState = JSON.stringify(state);
localStorage.setItem('state', serializedState);
} catch (e) {
console.log('Could not save state');
}
};
export const loadFromLocalStorage = () => {
try {
const serializedState = localStorage.getItem('state');
if (serializedState === null) {
return
}
return JSON.parse(serializedState);
} catch {
return
}
};
\ No newline at end of file
import {
FETCH_CATEGORIES_SUCCESS, FETCH_ERROR
} from "../actionTypes";
const initialState = {
categories: [],
error: null
};
const categoriesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_CATEGORIES_SUCCESS:
return {...state, categories: action.categories};
case FETCH_ERROR:
return {...state, error: action.error};
default:
return state;
}
};
export default categoriesReducer;
\ No newline at end of file
import {
FETCH_ERROR, FETCH_PRODUCTS_SUCCESS,
GET_FULL_PRODUCT_SUCCESS
} from "../actionTypes";
const initialState = {
fullProduct: {},
error: null,
products:[]
};
const productsReducer = (state = initialState, action) => {
switch (action.type) {
case GET_FULL_PRODUCT_SUCCESS:
return {...state, fullProduct: action.product};
case FETCH_PRODUCTS_SUCCESS:
return {...state, products: action.products};
case FETCH_ERROR:
return {...state, error: action.error};
default:
return state;
}
};
export default productsReducer;
import {
LOGIN_USER_FAILURE,
LOGIN_USER_SUCCESS,
LOGOUT_USER,
REGISTER_USER_FAILURE,
REGISTER_USER_SUCCESS
} from "../actionTypes";
const initialState = {
registerError: null,
loginError: null,
user: null
};
const usersReducer = function (state = initialState, action) {
switch(action.type) {
case REGISTER_USER_SUCCESS:
return {...state, registerError: null};
case REGISTER_USER_FAILURE:
return {...state, registerError: action.error};
case LOGIN_USER_SUCCESS:
return {...state, loginError: null, user: action.user};
case LOGIN_USER_FAILURE:
return {...state, loginError: action.error};
case LOGOUT_USER:
return {...state, user: null};
default:
return state;
}
};
export default usersReducer;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment