123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- import logo from './logo.svg';
- import './App.scss';
- import thunk from 'redux-thunk';
- import {createStore, combineReducers, applyMiddleware} from 'redux';
- import {Provider, connect} from 'react-redux';
- import {useEffect, useState} from 'react';
- import { Router, Route, Link, Redirect, match, BrowserRouter } from 'react-router-dom';
- import createHistory from "history/createBrowserHistory";
- const actionPending = name => ({type: 'PROMISE', status: 'PENDING', name});
- const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload});
- const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error});
- const actionPromise = (name, promise) =>
- async dispatch => {
- dispatch(actionPending(name)); // 1. {delay1000: {status: 'PENDING'}}
- try {
- let payload = await promise;
- dispatch(actionResolved(name, payload));
- return payload;
- } catch (error) {
- dispatch(actionRejected(name, error));
- }
- };
- const getGQL = url =>
- (query, variables = {}) =>
- fetch(url, {
- //метод
- method: 'POST',
- headers: {
- //заголовок content-type
- 'Content-Type': 'application/json',
- ...(localStorage.authToken ? {'Authorization': 'Bearer ' + localStorage.authToken} :
- {})
- },
- //body с ключами query и variables
- body: JSON.stringify({query, variables})
- })
- .then(res => res.json())
- .then(data => {
- if (data.errors && !data.data)
- throw new Error(JSON.stringify(data.errors));
- return data.data[Object.keys(data.data)[0]];
- });
- const backendURL = 'http://shop-roles.asmer.fs.a-level.com.ua';
- const gql = getGQL(backendURL + '/graphql');
- function jwtDecode(token) {
- try {
- let decoded = token.split('.');
- decoded = decoded[1];
- decoded = atob(decoded);
- decoded = JSON.parse(decoded);
- return decoded;
- } catch (e) {
- return;
- }
- }
- //скопировать
- //3 редьюсера
- //экшоны к ним
- //
- const actionRootCats = () =>
- actionPromise('rootCats', gql(`query {
- CategoryFind(query: "[{\\"parent\\":null}]"){
- _id name
- }
- }`));
- const actionCartAdd = (good, count = 1) => ({type: 'CART_ADD', good, count});
- const actionCartRemove = (good, count = 1) => ({type: 'CART_REMOVE', good, count});
- const actionCartChange = (good, count = 1) => ({type: 'CART_CHANGE', good, count});
- const actionCartClear = (good, count = 1) => ({type: 'CART_CLEAR', good, count});
- function cartReducer(state = {}, {type, good = {}, count = 1}) {
- const {_id} = good;
- const types = {
- CART_ADD() {
- return {
- ...state,
- [_id]: {good, count: count + (state[_id]?.count || 0)}
- };
- },
- CART_REMOVE() {
- let newState = {...state};
- delete newState[_id];
- return {
- ...newState
- };
- },
- CART_CHANGE() {
- return {
- ...state,
- [_id]: {good, count}
- };
- },
- CART_CLEAR() {
- return {};
- },
- };
- if (type in types)
- return types[type]();
- return state;
- }
- function authReducer(state, {type, token}) {
- if (!state) {
- if (localStorage.authToken) {
- type = 'AUTH_LOGIN';
- token = localStorage.authToken;
- } else {
- return {};
- }
- }
- if (type === 'AUTH_LOGIN') {
- let auth = jwtDecode(token);
- if (auth) {
- localStorage.authToken = token;
- return {token, payload: auth};
- }
- }
- if (type === 'AUTH_LOGOUT') {
- localStorage.authToken = '';
- return {};
- }
- return state;
- }
- function promiseReducer(state = {}, {type, name, status, payload, error}) {
- if (type === 'PROMISE') {
- return {
- ...state,
- [name]: {status, payload, error}
- };
- }
- return state;
- }
- // Actions =============================
- // Логин и логаут
- const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token});
- const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'});
- const actionLogin = (login = 'tst', password = '123') =>
- actionPromise('login', gql(`query ($login:String, $password:String){ login(login:$login, password:$password)}`, {
- 'login': login,
- 'password': password
- }));
- const actionFullLogin = (login = 'tst', password = '123') =>
- async dispatch => {
- let token = await dispatch(actionLogin(login, password));
- console.log(token);
- if (token) {
- dispatch(actionAuthLogin(token));
- }
- };
- // Регистрация
- const actionRegister = (login = 'tst', password = '123') =>
- actionPromise('login', gql(`mutation reg($login:String, $password:String) {
- UserUpsert(user:{login:$login, password:$password, nick:$login}){
- _id login
- }
- }`, {'login': login, 'password': password}));
- const actionFullRegister = (login = 'tst', password = '123') =>
- async dispatch => {
- console.log(login, password);
- await dispatch(actionRegister(login, password));
- await dispatch(actionFullLogin(login, password));
- };
- // Корневые категории
- // Товары категории
- const actionCatById = (_id) =>
- actionPromise('catById', gql(`query ($q: String){
- CategoryFindOne(query: $q){
- _id name goods {
- _id name price images {
- url
- }
- }
- subCategories {
- _id name
- }
- }
- }`, {q: JSON.stringify([{_id}])}));
- const actionGoodById = (_id) =>
- actionPromise('goodById', gql(`query ($good:String) {
- GoodFindOne(query:$good) {
- _id name price images {
- url
- }
- }
- }`, {good: JSON.stringify([{_id}])}));
- const store = createStore(combineReducers(
- {
- promise: promiseReducer,
- auth: authReducer,
- cart: cartReducer
- }),
- applyMiddleware(thunk));
- store.subscribe(() => console.log(store.getState()));
- store.dispatch(actionRootCats());
- const Logo = () =>
- <Link to="/">
- <img src={logo} className="Logo" alt="logo"/>
- </Link>;
- const Header = () =>
- <header className="header">
- <Logo/>
- <Login/>
- <CKoshik/>
- </header>;
- const CategoryListItem = ({_id, name}) =>
- <li>
- <Link to={`/category/${_id}`}>{name}</Link>
- </li>;
- const CategoryList = ({cats}) => {
- return (
- <ul>
- {cats.map((item) => <CategoryListItem key={item._id} {...item}/>)}
- </ul>
- );
- };
- const CCategoryList = connect(state => ({cats: state.promise.rootCats?.payload || []}))(CategoryList);
- const PageGood = ({good: {name, price, images}, match: {params: {_id}}, getData}) => {
- const isFirstImgExists = images && images[0] && images[0].url;
- useEffect(() => {
- getData(_id);
- }, []);
- return (
- <div>
- <h2>{name}</h2>
- {isFirstImgExists && <img src={`${backendURL}/${images[0].url}`} alt={name}/>}
- <strong>{price} грн</strong>
- </div>
- );
- };
- const CPageGood = connect(state => ({
- good: state.promise.goodById?.payload ?? [],
- }), {getData: actionGoodById})(PageGood);
- const Aside = () =>
- <aside className="aside">
- <CCategoryList/>
- </aside>;
- const GoodCard = ({good: {_id, name, price, images}, onAdd}) =>
- <li className="GoodCard">
- <h2>{name}</h2>
- {images && images[0] && images[0].url && <img src={`${backendURL}/${images[0].url}`}/>}
- <strong>{price} грн</strong>
- <button onClick={() => onAdd({_id, name, price, images})}>+</button>
- <Link to={`/good/${_id}`}>
- Подробнее
- </Link>
- </li>;
- const Login = () =>
- <Link to="/login">
- <h2 className="login">Личный кабинет</h2>
- </Link>;
- const CGoodCard = connect(null, {
- onAdd: actionCartAdd
- })(GoodCard);
- const Koshik = ({cart}) => {
- let goodsInCart = cart;
- let allGoodsInCart = 0;
- for (let key in goodsInCart) {
- allGoodsInCart += goodsInCart[key].count;
- }
- return (
- <h2 className="koshikCounter">
- <Link to="/cart">В корзине:{allGoodsInCart}
- </Link>
- </h2>
- );
- };
- const CKoshik = connect(({cart}) => ({cart}))(Koshik);
- const Category = ({cat: {name, goods = []} = {}}) => {
- return (
- <div className="Category">
- <h1>{name}</h1>
- <ul className="ul">
- {(goods || []).map(good => <CGoodCard key={good._id} good={good}/>)}
- </ul>
- </div>
- );
- };
- const CCategory = connect(state => ({cat: state.promise.catById?.payload || {}}))(Category);
- const Cart = ({cart, onCartChange, onCartRemove}) => {
- const error = typeof cart === 'undefined';
- return (
- <div className="Cart">
- <h1>Корзина</h1>
- <div className="containerForCart">
- <div className="wrapperForName">
- {error === false &&
- (cart.map((good) => {
- return (
- <div className="name" key={good.good.name}>{good.good.name}</div>
- );
- })
- )
- }
- </div>
- <div className="wrapperForCount">
- {error === false &&
- (cart.map((good) => {
- return <input
- className="count"
- type="number"
- defaultValue={good.count}
- onChange={(e) => {
- onCartChange(good.good, +e.target.value);
- }
- }
- key={good.good._id}
- />;
- })
- )}
- </div>
- <div className="wrapperForName">
- {error === false &&
- (cart.map((good) => {
- return (
- <button key={good.good._id} onClick={() => onCartRemove(good.good, good.count)
- }>
- Удалить {good.good.name}</button>);
- })
- )
- }
- </div>
- </div>
- </div>
- );
- };
- const CCart = connect(
- state => ({cart: Object.values(state.cart) || []}),
- {
- onCartChange: actionCartChange,
- onCartRemove: actionCartRemove
- })(Cart);
- const LoginForm = ({onCartLogin}) => {
- const [login, setLogin] = useState('');
- const [password, setPassword] = useState('');
- const isDisabled = login === '' || password === '';
- console.log(login, password, isDisabled);
- const onChange = (event) => {
- event.target.name === 'login' ? setLogin(event.target.value) : setPassword(event.target.value);
- };
- return (
- <div className="inputWrapper">
- <h2>Вход в личный кабинет</h2>
- <input
- className="inputLogin"
- name="login"
- style={{borderColor: isDisabled ? 'red' : ''}}
- placeholder="введите логин"
- type="text"
- onChange={onChange}
- />
- <input
- className="inputPsw"
- name="password"
- style={{borderColor: isDisabled ? 'red' : ''}}
- placeholder="введите пароль"
- type="password"
- onChange={onChange}
- />
- <button
- className="buttonLogin"
- disabled={isDisabled}
- onClick={
- () => {
- onCartLogin(login, password);
- }}>Login
- </button>
- <h4>Вы еще не зарегистрированы?</h4>
- <Link to="/registration">Зарегистрироваться</Link>
- </div>
- );
- };
- const CLoginForm = connect(
- null,
- {
- onCartLogin: actionFullLogin
- })(LoginForm);
- const RegisterForm = ({onCartRegister}) => {
- const [login, setLogin] = useState('');
- const [password, setPassword] = useState('');
- const isDisabled = login === '' || password === '';
- console.log(login, password, isDisabled);
- const onChange = (event) => {
- event.target.name === 'login' ? setLogin(event.target.value) : setPassword(event.target.value);
- };
- return (
- <div className="inputWrapper">
- <h2>Регистрация</h2>
- <input
- className="inputLogin"
- name="userName"
- style={{borderColor: isDisabled ? 'red' : ''}}
- placeholder="введите имя"
- type="text"
- onChange={onChange}
- />
- <input
- className="inputLogin"
- name="login"
- style={{borderColor: isDisabled ? 'red' : ''}}
- placeholder="*введите логин"
- type="text"
- onChange={onChange}
- />
- <input
- className="inputPsw"
- name="password"
- style={{borderColor: isDisabled ? 'red' : ''}}
- placeholder="*введите пароль"
- type="password"
- onChange={onChange}
- />
- <button
- className="buttonLogin"
- disabled={isDisabled}
- onClick={
- () => {
- onCartRegister(login, password);
- }}>Login
- </button>
- <Link to="/login">Войти в личный кабинет</Link>
- </div>
- );
- };
- const CRegisterForm = connect(
- null,
- {
- onCartRegister: actionFullRegister
- })(RegisterForm);
- const PageMain = () =>
- <h1>Главная страничка</h1>;
- const PageCategory = ({match: {params: {_id}}, getData}) => {
- useEffect(() => {
- getData(_id);
- }, [_id]);
- return (
- <div>
- <CCategory/>
- </div>
- );
- };
- const CPageCategory = connect(null, {getData: actionCatById})(PageCategory);
- const Main = () =>
- <main className="main">
- <Aside/>
- <Content>
- <Route path="/" component={PageMain} exact/>
- <Route path="/category/:_id" component={CPageCategory} exact/>
- <Route path="/cart" component={CCart} exact/>
- <Route path="/login" component={CLoginForm} exact/>
- <Route path="/registration" component={CRegisterForm} exact/>
- <Route path="/good/:_id" component={CPageGood} exact/>
- {/*<CCategory/>*/}
- </Content>
- </main>;
- const Content = ({children}) =>
- <div className="Content">
- {children}
- </div>;
- const Footer = () =>
- <footer className="Footer">
- <Logo/>
- </footer>;
- const history = require('history').createBrowserHistory();
- //import {Provider, connect} from 'react-redux';
- function App() {
- return (
- <Router history={history}>
- <Provider store={store}>
- <div className="App">
- <Header/>
- <Main/>
- <Footer/>
- </div>
- </Provider>
- </Router>
- );
- }
- export default App;
|