Browse Source

homework 'javascript module' done

holevchuk.evgeny 1 year ago
parent
commit
49f62bd390
2 changed files with 920 additions and 0 deletions
  1. 179 0
      javascript_module/index.html
  2. 741 0
      javascript_module/index.js

+ 179 - 0
javascript_module/index.html

@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>Title</title>
+	<style>
+		html, body {
+			margin: 0;
+			padding: 0;
+		}
+
+		#main {
+			max-width: 70%;
+		}
+
+		.header {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			position: sticky;
+			top: 0;
+			min-height: 60px;
+            padding-left: 20px;
+            padding-right: 20px;
+			background-color: #afafaf;
+		}
+
+		.good-images {
+			display: flex;
+			align-items: center;
+			column-gap: 50px;
+            margin-top: 50px;
+		}
+
+		.good-images__element {
+            max-width: 100%;
+            max-height: 300px;
+		}
+
+		.card__image {
+            max-width: 100%;
+            max-height: 300px;
+		}
+
+        #mainContainer {
+            display: flex;
+            padding: 20px;
+        }
+
+        .cart,
+        .order-cart-group,
+        .order-cart__elements {
+	        display: flex;
+	        flex-direction: column;
+	        row-gap: 20px;
+        }
+
+        .cart__element,
+        .order-cart__element {
+            padding: 20px;
+	        border: 1px solid #ebebeb;
+        }
+
+        .cart__figure,
+        .order-cart__figure {
+	        display: flex;
+	        column-gap: 30px;
+	        margin: 0;
+        }
+
+        .cart__image,
+        .order-cart__image {
+	        max-width: 100px;
+	        max-height: 100px;
+        }
+
+        .cart__price {
+	        display: block;
+            margin-top: 20px;
+        }
+
+        .cart__name {
+	        font-size: 18px;
+	        display: block;
+        }
+
+        fieldset {
+	        margin: 0;
+	        padding: 0;
+	        border: 0 none;
+        }
+
+        .cart__quantity {
+            display: flex;
+	        height: 40px;
+        }
+
+        .cart__quantity-button {
+            background-color: #fff;
+            border: 1px solid #ebebeb;
+            color: #333;
+            font-size: 18px;
+            height: inherit;
+            transition: background-color 225ms ease;
+            width: 40px;
+	        cursor: pointer;
+        }
+
+        .cart__quantity-button:disabled {
+	        background-color: rgba(51, 51, 51, 0.15);
+	        cursor: not-allowed;
+        }
+
+        .cart__quantity-button--decrease {
+            border-radius: 3px 0 0 3px;
+            border-right: 0;
+        }
+
+        .cart__quantity-button--increase {
+            border-left: 0;
+            border-radius: 0 3px 3px 0;
+        }
+
+        .cart__quantity-amount {
+	        box-sizing: border-box;
+            appearance: none;
+	        display: flex;
+	        align-items: center;
+	        justify-content: center;
+            background-color: #fff;
+            border: 1px solid #ebebeb;
+            color: #333;
+            font-size: 13px;
+            border-radius: 0;
+            height: inherit;
+            padding: 0 5px;
+            text-align: center;
+            width: 50px;
+            transition: border-color 225ms ease;
+        }
+
+        .cart__delete-button {
+            margin-top: 20px;
+	        width: 120px;
+	        height: 40px;
+	        cursor: pointer;
+        }
+
+        .cart-button {
+	        font-size: 18px;
+	        width: 200px;
+	        height: 60px;
+	        cursor: pointer;
+        }
+
+        #aside {
+            width: 30%;
+        }
+        #aside > a{
+            display: block;
+        }
+	</style>
+</head>
+<body>
+<header class="header">
+	<div id='cartIcon'></div>
+	<div id='authSection'></div>
+</header>
+	<div id='mainContainer'>
+		<aside id='aside'>
+			Категории
+		</aside>
+		<main id='main'>
+			Контент
+		</main>
+	</div>
+	<script src="index.js"></script>
+</body>
+</html>

+ 741 - 0
javascript_module/index.js

@@ -0,0 +1,741 @@
+function createStore(reducer){
+	let state       = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
+	let cbs         = []                     //массив подписчиков
+
+	const getState  = () => state            //функция, возвращающая переменную из замыкания
+	const subscribe = cb => (cbs.push(cb),   //запоминаем подписчиков в массиве
+		() => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
+
+	const dispatch  = action => {
+		if (typeof action === 'function'){ //если action - не объект, а функция
+			return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
+		}
+		const newState = reducer(state, action) //пробуем запустить редьюсер
+		if (newState !== state){ //проверяем, смог ли редьюсер обработать action
+			state = newState //если смог, то обновляем state
+			for (let cb of cbs)  cb() //и запускаем подписчиков
+		}
+	}
+
+	return {
+		getState, //добавление функции getState в результирующий объект
+		dispatch,
+		subscribe //добавление subscribe в объект
+	}
+}
+
+function promiseReducer(state={}, {type, name, status, payload, error}){
+	////?????
+	//ОДИН ПРОМИС:
+	//состояние: PENDING/FULFILLED/REJECTED
+	//результат
+	//ошибка:
+	//{status, payload, error}
+	//{
+	//       name1:{status, payload, error}
+	//       name2:{status, payload, error}
+	//       name3:{status, payload, error}
+	//}
+	if (type === 'PROMISE'){
+		return {
+			...state,
+			[name]:{status, payload, error}
+		}
+	}
+	return state
+}
+
+const actionPending   = (name) => ({type: 'PROMISE', status: 'PENDING', name})
+const actionFulfilled = (name, payload) => ({type: 'PROMISE', status: 'FULFILLED', name, payload})
+const actionRejected  = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
+
+const actionPromise   = (name, promise) =>
+	async dispatch => {
+		try {
+			dispatch(actionPending(name))
+			let payload = await promise
+			dispatch(actionFulfilled(name, payload))
+			return payload
+		}
+		catch(e){
+			dispatch(actionRejected(name, e))
+		}
+	}
+
+const goToMainPage = () => location.href = location.href.split("#")[0];
+
+const checkAuthToken = () => {
+	const headers = {
+		"Content-Type": "application/json",
+		"Accept": "application/json",
+	}
+	if(localStorage.getItem('authToken')) {
+		return {
+			...headers,
+			"Authorization": `Bearer ${localStorage.getItem('authToken')}`
+		}
+	} else {
+		return headers;
+	}
+}
+
+const getGQL = url =>
+	(query, variables= {}) =>
+		fetch(url, {
+			method: 'POST',
+			headers: checkAuthToken(),
+			body:JSON.stringify({query, variables})
+		}).then(res => res.json())
+			.then(data => {
+				try {
+					if(!data.data && data.errors) {
+						throw new SyntaxError(`SyntaxError - ${JSON.stringify(Object.values(data.errors)[0])}`);
+					}
+					return Object.values(data.data)[0];
+				} catch (e) {
+					console.error(e);
+				}
+			});
+
+function jwtDecode(token){
+	try {
+		return JSON.parse(atob(token.split('.')[1]))
+	}
+	catch(e){
+	}
+}
+
+
+function authReducer(state={}, {type, token}){
+	//{
+	//  token,payload (раскодированный токен)
+	//} или, если не залогинены
+	//{
+	// нихрена, т. .е. пустой объект
+	//}
+	if (type === 'AUTH_LOGIN'){ //то мы логинимся
+		const payload = jwtDecode(token)
+		if (payload){
+			return {
+				token,
+				payload
+			}
+		}
+	}
+	if (type === 'AUTH_LOGOUT'){ //мы разлогиниваемся
+		return {}
+	}
+	return state
+}
+
+const actionAuthLogout = () =>
+	dispatch => {
+		dispatch({type: 'AUTH_LOGOUT'});
+		localStorage.removeItem('authToken');
+		goToMainPage()
+	}
+
+const actionAuthLogin = (token) =>
+	(dispatch, getState) => {
+		const oldState = getState()
+		dispatch({type: 'AUTH_LOGIN', token})
+		const newState = getState()
+		if (oldState !== newState)
+			localStorage.setItem('authToken', token)
+	}
+
+function cartReducer(state={}, {type, amount=1, good}){
+	/*
+	{
+		id1: {amount, good: {объект с бэка с _id, description, name, price}},
+		id2: {amount, good: {объект с бэка с _id, description, name, price}},
+		id3: {amount, good: {объект с бэка с _id, description, name, price}}
+	}
+	*/
+
+	if (type === 'CART_ADD'){
+		return {
+			...state,
+			[good._id]: {good, amount: (state[good._id]?.amount || 0) + amount }
+		}
+	}
+	if (type === 'CART_SET'){
+		return {
+			...state,
+			[good._id]: {good, amount}
+		}
+	}
+	if (type === 'CART_DELETE'){
+		const {[good._id]: skip,...newState} = state
+		//const newState = { ...state }
+		//delete newState[good._id]
+		return newState
+	}
+	if (type === 'CART_CLEAR'){
+		return {}
+	}
+	//if (type === ''){
+
+	//}
+
+	return state
+}
+
+const actionCartAdd    = (good, amount=1) => ({type: 'CART_ADD', good, amount})
+const actionCartSet    = (good, amount=1) => ({type: 'CART_SET', good, amount})
+const actionCartClear  = ()               => ({type: 'CART_CLEAR'})
+const actionCartDelete = (good)           => ({type: 'CART_DELETE', good})
+
+
+
+function combineReducers(reducers){
+	function totalReducer(state={}, action){
+		//{
+		//promise:{
+		//name1:{status, payload, error},
+		//name2:{status, payload, error}
+		//},
+		//auth: {
+		//token, payload
+		//}
+		//}
+		const newTotalState = {}
+		for (const [reducerName, reducer] of Object.entries(reducers)){
+			const newSubState = reducer(state[reducerName], action)
+			if (newSubState !== state[reducerName]){
+				newTotalState[reducerName] = newSubState
+			}
+		}
+		if (Object.keys(newTotalState).length){
+			return {...state, ...newTotalState}
+		}
+
+		return state
+	}
+
+	return totalReducer
+}
+
+function localStoredReducer(reducer, localStorageKey){
+	function wrapperReducer(state, action){
+		if (state === undefined){ //если загрузка сайта
+			try {
+				return JSON.parse(localStorage[localStorageKey]) //пытаемся распарсить сохраненный
+				//в localStorage state и подсунуть его вместо результата редьюсера
+			}
+			catch(e){ } //если распарсить не выйдет, то код пойдет как обычно:
+		}
+		const newState = reducer(state, action)
+		localStorage.setItem(localStorageKey, JSON.stringify(newState)) //сохраняем состояние в localStorage
+		return newState
+	}
+	return wrapperReducer
+}
+
+
+const reducers = {
+	auth: authReducer,
+	cart: localStoredReducer(cartReducer, 'cart'), //в localStorage должен появится ключ cart с JSON стейта корзины
+	promise: localStoredReducer(promiseReducer, 'promise'),
+}
+
+const totalReducer = combineReducers(reducers)
+
+
+const store = createStore(totalReducer) //не забудьте combineReducers если он у вас уже есть
+store.subscribe(() => console.log(store.getState()))
+
+
+const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/'
+
+const gql = getGQL(backendURL + 'graphql')
+
+const rootCategories = () =>
+	gql(`query cadz($q:String) {
+        CategoryFind(query:$q){
+            _id name
+        }
+    }`, {q: JSON.stringify([{parent: null}])})
+
+const actionRootCategories = () =>
+	actionPromise('rootCategories', rootCategories())
+
+const categoryById = _id => //добавьте сюда подкатегории и родителя - пригодятся
+	gql(`query catById($qCat:String){
+              CategoryFindOne(query:$qCat){
+                _id name
+                parent {
+                  _id name
+                }
+                subCategories {
+                  name _id parent {
+                    _id name
+                  }
+                }
+                goods{
+                  _id name price images{
+                    url
+                  } 
+                }
+              }  
+            }`, {qCat: JSON.stringify([{_id}])})
+
+const actionCategoryById = _id =>
+	actionPromise('catById', categoryById(_id))
+
+const goodById = _id =>
+	gql(`query goodById($goodId:String) {
+				GoodFindOne(query:$goodId) {
+				    _id name price description images {
+				          url
+				        }
+				    }
+				}`, {goodId: JSON.stringify([{_id}])})
+
+const actionGoodById = _id =>
+	actionPromise('goodById', goodById(_id));
+
+const actionOrder = () =>
+	async (dispatch, getState) => {
+		const order = Object.values(getState().cart).map(orderGoods => ({good: {_id: orderGoods.good._id}, count: orderGoods.amount}));
+		const myOrder = await dispatch(actionPromise('myOrder', gql(`mutation myOrder($order:OrderInput) {
+														  OrderUpsert(order:$order) {
+														    _id createdAt total
+														  }
+														}`, {order: {orderGoods: order}})));
+		if(myOrder) {
+			dispatch(actionCartClear());
+		}
+	}
+
+const orders = () =>
+	gql(`query myOrders {
+				  OrderFind(query:"[{}]"){
+				    _id total orderGoods{
+				      price count total good{
+				        _id name images{
+				          url
+				        }
+				      }
+				    }
+				  }
+				}`, {})
+
+const actionOrders = () =>
+	actionPromise('myOrders', orders())
+
+const actionLogin = (login, password) =>
+	actionPromise('login', gql(`query log($login:String, $password:String) {
+					  login(login:$login, password:$password)
+					}`, {login, password}));
+
+const actionRegister = (login, password) =>
+	actionPromise('register', gql(`mutation register($login:String, $password:String) {
+												  UserUpsert(user:{login:$login, password:$password}) {
+												    _id login createdAt
+												  }
+												}`, {login, password}));
+
+const actionFullLogin = (login, password) =>
+	async dispatch => {
+		const token = await dispatch(actionLogin(login, password))
+		if (token){
+			dispatch(actionAuthLogin(token));
+			goToMainPage()
+		}
+	}
+
+const actionFullRegister = (login, password) =>
+	async dispatch => {
+		const user = await dispatch(actionRegister(login, password))
+		if(user) {
+			dispatch(actionFullLogin(login, password))
+		}
+	}
+
+const actionFullOrders = () =>
+	async dispatch => {
+		await dispatch(actionOrders());
+		if(Object.keys(store.getState().auth).length === 0) {
+			goToMainPage()
+		}
+	}
+
+function Password(parent, open) {
+
+	const input = document.createElement('input');
+	input.id = 'password'
+	input.type = 'password';
+	parent.appendChild(input);
+
+	const button = document.createElement('button');
+	button.type = 'button';
+	button.textContent = 'показать';
+	parent.appendChild(button);
+
+	button.addEventListener('click', () => {
+		this.setOpen(open !== true);
+	});
+
+	this.setValue = newValue => input.value = newValue;
+
+	this.getValue = () => input.value;
+
+	this.setOpen = openUpdate => {
+		open = openUpdate;
+		if(typeof this.onOpenChange === 'function') {
+			this.onOpenChange(openUpdate);
+		}
+		button.textContent = (openUpdate) ? 'показать' : 'скрыть';
+		input.type = (openUpdate) ? 'password' : 'text';
+	}
+
+	this.getOpen = () => open;
+
+	input.addEventListener('input', event => {
+		if (typeof this.onChange === 'function'){
+			this.onChange(event.target.value);
+		}
+	});
+}
+
+function LoginForm(parent) {
+
+	const createDivider = () => parent.appendChild(document.createElement('br'));
+
+	const input = document.createElement('input');
+	input.id = 'login';
+	input.type = 'text';
+	parent.appendChild(input);
+
+	const button = document.createElement('button');
+	button.type = 'button';
+	button.textContent = 'Логин';
+	button.disabled = true;
+
+	input.addEventListener('input', event => {
+		if (typeof this.onChange === 'function'){
+			this.onChange(event.target.value);
+		}
+	});
+
+	button.addEventListener('click', event => {
+		if (typeof this.onLogin === 'function') {
+			this.onLogin(event.target);
+		}
+	});
+
+	this.getLogin = () => input.value;
+
+	createDivider();
+	const password = new Password(parent, true);
+
+	const getPassword = () => password.getValue();
+
+	createDivider();
+	parent.appendChild(button);
+
+	const isDisabled = () => button.disabled = (!(getPassword() !== '' && this.getLogin() !== ''));
+
+	password.onChange = () => isDisabled();
+
+	this.onChange = () => isDisabled();
+
+	this.setButtonText = newText => button.textContent = newText;
+}
+
+store.subscribe(() => {
+	const rootCats = store.getState().promise.rootCategories?.payload
+	if (rootCats){
+		aside.innerHTML = ''
+		for (let {_id, name} of rootCats){
+			const a = document.createElement('a')
+			a.innerText = name
+			a.href      = `#/category/${_id}`
+			aside.append(a)
+		}
+	}
+})
+
+store.subscribe(() => {
+	const catById = store.getState().promise.catById?.payload
+	const [,route] = location.hash.split('/')
+	if (catById && route === 'category'){
+		const {name, goods, parent, subCategories} = catById;
+		main.innerHTML = `<h1>${name}</h1>`;
+
+		if(parent) {
+			const {_id, name} = parent;
+			const breadcrumbs = document.createElement('a');
+			breadcrumbs.innerText = name;
+			breadcrumbs.href = `#/category/${_id}`;
+			main.prepend(breadcrumbs);
+		}
+
+		if(subCategories) {
+			const listSubCategories = document.createElement('ul');
+			for (let {_id, name} of subCategories) {
+				listSubCategories.innerHTML += `
+					<li>
+						<a href="#/category/${_id}">${name}</a>
+					</li>
+				`;
+			}
+			main.append(listSubCategories);
+		}
+
+		for (let good of goods){
+			const {_id, name, price, images} = good
+			const a = document.createElement('a')
+			a.classList.add('card')
+			a.innerHTML = `
+                <div>
+                    <img class="card__image" alt="${name}" src="${backendURL + images[0]?.url}" />
+                    <h2>${name}</h2>
+                    <strong>${price}</strong>
+                </div>
+            `
+			a.href      = `#/good/${_id}`
+
+			const button = document.createElement('button')
+			button.type = 'button';
+			button.innerText = 'добавить в корзину'
+			button.onclick = () => {
+				store.dispatch(actionCartAdd(good))
+			}
+			main.append(a)
+			main.append(button)
+		}
+	}
+})
+
+store.subscribe(() => {
+	const goodById = store.getState().promise.goodById?.payload
+	const [,route] = location.hash.split('/')
+	if (goodById && route === 'good'){
+		const {name, description, price, images} = goodById
+		main.innerHTML = `
+			<div>
+				<h1>${name}</h1>
+				<p>${description}</p>
+				<strong>${price}</strong>
+			</div>
+		`;
+		const button = document.createElement('button')
+		button.type = 'button';
+		button.innerText = 'добавить в корзину'
+		button.onclick = () => {
+			store.dispatch(actionCartAdd(goodById))
+		}
+		main.append(button);
+		let imageGroup = document.createElement('div');
+		imageGroup.classList.add('good-images')
+		for (const img in images) {
+			imageGroup.innerHTML += `
+				<div>
+					<img class="good-images__element" src="${backendURL + images[img]?.url}" alt="${name} photo-${img}">
+				</div>
+			`
+		}
+		main.append(imageGroup)
+	}
+})
+
+const drawCart = () => {
+	const cart = store.getState().cart;
+	const [,route] = location.hash.split('/');
+	if (cart && route === 'cart'){
+		if(Object.keys(cart).length === 0) {
+			main.innerHTML = '<h1>Корзина пустая</h1>';
+		} else {
+			main.innerHTML = '';
+			const cartBlock = document.createElement('div');
+			cartBlock.classList.add('cart');
+			const totalAmountByPosition = [];
+			for (const good of Object.values(cart)) {
+				const {_id, name, price, images} = good.good;
+				totalAmountByPosition.push(price * good.amount);
+				const cartElement = document.createElement('div');
+				cartElement.classList.add('cart__element')
+				cartElement.innerHTML = `
+					<figure class="cart__figure">
+						<a class="cart__image-link" href="#/good/${_id}">
+							<img class="cart__image" src="${backendURL + images[0]?.url}" alt="${name}">
+						</a>
+						<figcaption class="cart__caption">
+							<a href="#/good/${_id}" class="cart__name">${name}</a>
+							<strong class="cart__price">Цена - ${price}</strong>
+							<p class="cart__amount">Количество - ${good.amount} шт.</p>
+							<p class="cart__amount-total">Итого по позиции - ${price * good.amount}</p>
+						</figcaption>
+					</figure>
+				`;
+				const cartQuantity = document.createElement('fieldset');
+				cartQuantity.classList.add('cart__quantity');
+				const cartQuantityAmount = document.createElement('span');
+				const incDecButton = (text, classStr, incDec) => {
+					const button = document.createElement('button');
+					button.type = 'button';
+					button.id = `${(incDec ? 'decrease' : 'increase')}`
+					button.innerText = text;
+					button.classList.add('cart__quantity-button', `cart__quantity-button--${classStr}`);
+					button.onclick = () => {
+						store.dispatch(actionCartAdd(good.good, (incDec ? -1 : +1)));
+						if(cart[_id].amount > 1 && button.id === 'decrease') {
+							button.disabled = false
+						}
+					}
+					if(cart[_id].amount === 1 && button.id === 'decrease') {
+						button.disabled = true;
+					}
+					return button;
+				}
+				cartQuantityAmount.classList.add('cart__quantity-amount');
+				cartQuantityAmount.innerText = good.amount;
+				cartElement.append(cartQuantity);
+				cartQuantity.append(incDecButton('-', 'decrease', true));
+				cartQuantity.append(cartQuantityAmount);
+				cartQuantity.append(incDecButton('+', 'increase', false));
+				const deleteButton = document.createElement('button');
+				deleteButton.type = 'button'
+				deleteButton.innerText = 'Удалить товар'
+				deleteButton.classList.add('cart__delete-button')
+				deleteButton.onclick = () => {
+					store.dispatch(actionCartDelete(good.good))
+				}
+				cartElement.append(deleteButton);
+				cartBlock.append(cartElement);
+			}
+			main.append(cartBlock)
+			const totalPrice = document.createElement('p');
+			totalPrice.innerHTML = `Итого - <b>${totalAmountByPosition.reduce((a, b) => a + b, 0)}</b>`;
+			main.append(totalPrice);
+			const makeOrderButton = document.createElement('button');
+			makeOrderButton.type = 'button';
+			makeOrderButton.innerText = 'Оформить заказ';
+			makeOrderButton.classList.add('cart-button');
+			makeOrderButton.onclick = () => {
+				store.dispatch(actionOrder());
+			}
+			main.append(makeOrderButton)
+		}
+	}
+}
+
+store.subscribe(drawCart);
+
+store.subscribe(() => {
+	const orders = store.getState().promise.myOrders?.payload;
+	const [,route] = location.hash.split('/')
+	if(orders && route === 'orderhistory') {
+		main.innerHTML = '<h1>Мои заказы</h1>';
+		const orderCartGroup = document.createElement('div');
+		orderCartGroup.classList.add('order-cart-group');
+		for (const [index, value] of Object.entries(orders)) {
+			const orderCartGroupElement = document.createElement('div');
+			orderCartGroupElement.classList.add('order-cart-group__element')
+			orderCartGroupElement.innerHTML = `<h2>Заказ №${+index+1} (ID заказа - ${value._id})</h2>`;
+			const orderCartElements = document.createElement('div');
+			orderCartElements.classList.add('order-cart__elements');
+			const totalAll = document.createElement('p');
+			totalAll.innerHTML = `<p>Итого - <b>${value.total}</b></p>`;
+			for (const goodElement of Object.values(value.orderGoods)) {
+				const {price, count, total, good} = goodElement;
+				const orderCartElement = document.createElement('div');
+				orderCartElement.classList.add('order-cart__element');
+				orderCartElement.innerHTML = `
+					<figure class="order-cart__figure">
+						<a class="order-cart__image-link"
+					       href="#/good/${good._id}"
+				        >
+							<img class="order-cart__image"
+								 src="${backendURL + good.images[0]?.url}"
+								 alt="${good.name}"
+						    >
+						</a>
+						<figcaption class="order-cart__caption">
+							<a class="order-cart__headline-link"
+						       href="#/good/${good._id}"
+					        >
+								<h3 class="order-cart__headline">${good.name}</h3>
+							</a>
+							
+							<p>Цена - ${price}</p>
+							<p>Количество - ${count}</p>
+							<p>Итого по позиции - ${total}</p>
+						</figcaption>
+					</figure>
+				`;
+				orderCartElement.classList.add('order-cart__element');
+				orderCartElements.append(orderCartElement)
+			}
+			orderCartGroupElement.append(orderCartElements);
+			orderCartGroupElement.append(totalAll);
+			orderCartGroup.append(orderCartGroupElement)
+		}
+		main.append(orderCartGroup)
+	}
+})
+
+const drawUserName = () => {
+	const buttonLogout = '<button onclick="store.dispatch(actionAuthLogout())" type="button">Выйти</button>';
+	const buttonLogin = '<a href="#/login/">Войти</a>';
+	const buttonRegister = '<a href="#/register/">Регистрация</a>';
+
+	authSection.innerHTML = store.getState().auth.token ? `Пользователь - <a href="#/orderhistory">${store.getState().auth.payload.sub.login}</a><div>${buttonLogout}</div>` :`Пользователь - <i>anon</i> <div>${buttonLogin}  ${buttonRegister}</div>`;
+}
+drawUserName() //работаем безусловно при перезагрузке страницы
+store.subscribe(drawUserName) //а так же при обновлении redux
+
+// честно стырил отсюда - https://gist.github.com/realmyst/1262561?permalink_comment_id=2299442#gistcomment-2299442
+const declOfNum = (n, titles) => titles[(n % 10 === 1 && n % 100 !== 11) ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2];
+
+const drawCardAmount = () => {
+	if(store.getState().cart && store.getState().auth.token) {
+		let totalAmount = Object.values(store.getState().cart).reduce((a, b) => a + b.amount, 0);
+		cartIcon.innerHTML = `<a href="#/cart">Корзина</a> - <b>${totalAmount}</b> ${declOfNum(totalAmount, ['товар', 'товара', 'товаров'])}`;
+	}
+}
+drawCardAmount()
+store.subscribe(drawCardAmount)
+
+store.dispatch(actionAuthLogin(localStorage.authToken))
+store.dispatch(actionRootCategories())
+
+//#/category/АЙДИШНИК
+//#/good/АЙДИШНИК
+window.onhashchange = () => {
+	const [,route, _id] = location.hash.split('/')
+
+	const routes = {
+		category() {
+			store.dispatch(actionCategoryById(_id))
+		},
+		good(){
+			store.dispatch(actionGoodById(_id))
+		},
+		login(){
+			main.innerHTML = '';
+			const loginForm = new LoginForm(main);
+			loginForm.onLogin = () => store.dispatch(actionFullLogin(login.value, password.value))
+		},
+		register(){
+			main.innerHTML = '';
+			const registerForm = new LoginForm(main);
+			registerForm.setButtonText('Регистрация');
+			registerForm.onLogin = () => store.dispatch(actionFullRegister(login.value, password.value));
+		},
+		cart(){
+			drawCart();
+		},
+		orderhistory(){
+			store.dispatch(actionFullOrders())
+		}
+	}
+
+	if (route in routes){
+		routes[route]()
+	}
+}
+
+window.onhashchange()