12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004 |
- // url of backend
- const url = 'http://shop-roles.node.ed.asmer.org.ua/graphql'
- // Вспомогательные функции ========================================================================================================
- // getGql - переделка из HW18 функции gql (делаем запрос на бэк)
- function getGql(endpoint) {
- return async function gql(query, variables = {}) {
- let headers = {
- 'Content-Type': 'application/json;charset=utf-8',
- 'Accept': 'application/json',
- }
- if (('authToken' in localStorage)) {
- headers.Authorization = 'Bearer ' + localStorage.authToken
- }
- let result = await fetch(endpoint, {
- method: 'POST',
- headers,
- body: JSON.stringify({
- query,
- variables
- })
- }).then(res => res.json())
- if (('errors' in result) && !('data' in result)) {
- throw new Error(JSON.stringify(result.errors))
- }
- result = Object.values(result.data)[0]
- return result
- }
- }
- const gql = getGql(url)
- //=========================================================================================================
- // localStoredReducer
- function localStoredReducer(originalReducer, localStorageKey) {
- function wrapper(state, action) {
- if (!state) {
- try {
- return JSON.parse(localStorage[localStorageKey])
- }
- catch (error) {
- }
- }
- const newState = originalReducer(state, action)
- localStorage[localStorageKey] = JSON.stringify(newState)
- return newState
- }
- return wrapper
- }
- // Redux ========================================================================================================
- // createStore
- 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,
- dispatch,
- subscribe
- }
- }
- // создание combineReducers
- function combineReducers(reducers) {
- function totalReducer(state = {}, action) {
- 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
- }
- // создание promiseReducer
- function promiseReducer(state = {}, { type, status, payload, error, nameOfPromise }) {
- if (type === 'PROMISE') {
- return {
- ...state,
- [nameOfPromise]: { status, payload, error }
- }
- }
- return state
- }
- // акшоны для promiseReducer
- const actionPending = nameOfPromise => ({ nameOfPromise, type: 'PROMISE', status: 'PENDING' })
- const actionFulfilled = (nameOfPromise, payload) => ({ nameOfPromise, type: 'PROMISE', status: 'FULFILLED', payload })
- const actionRejected = (nameOfPromise, error) => ({ nameOfPromise, type: 'PROMISE', status: 'REJECTED', error })
- const actionPromise = (nameOfPromise, promise) =>
- async dispatch => {
- dispatch(actionPending(nameOfPromise)) //сигнализируем redux, что промис начался
- try {
- const payload = await promise //ожидаем промиса
- dispatch(actionFulfilled(nameOfPromise, payload)) //сигнализируем redux, что промис успешно выполнен
- return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
- }
- catch (error) {
- dispatch(actionRejected(nameOfPromise, error)) //в случае ошибки - сигнализируем redux, что промис несложился
- }
- }
- // authReducer
- // раскодируем JWT-токен
- const jwtDecode = function (token) {
- try {
- let parseData = token.split('.')[1]
- return JSON.parse(atob(parseData))
- }
- catch (e) {
- return undefined
- }
- }
- function authReducer(state = {}, { type, token }) {
- if (type === 'AUTH_LOGIN') {
- let payload = jwtDecode(token)
- return state = {
- token,
- payload
- }
- }
- if (type === 'AUTH_LOGOUT') {
- localStorage.removeItem('authToken')
- return {}
- }
- return state
- }
- // акшон для логинизации
- const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
- // акшон для раззлогинивания
- const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
- // cartReducer
- function cartReducer(state = {}, { type, count, good }) {
- if (type === 'CART_ADD') {
- return {
- ...state,
- [good._id]: {
- good,
- count: (state[good._id] ? state[good._id].count + count : count)
- }
- }
- }
- if (type === 'CART_SUB') {
- if (state[good._id]) {
- let newCount = state[good._id].count - count
- if (newCount > 0) {
- return {
- ...state,
- [good._id]: {
- good,
- count: newCount
- }
- }
- } else {
- delete state[good._id]
- return { ...state }
- }
- } else {
- return undefined
- }
- }
- if (type === 'CART_DEL') {
- delete state[good._id]
- return { ...state }
- }
- if (type === 'CART_SET') {
- if (count > 0) {
- return {
- ...state,
- [good._id]: {
- good,
- count
- }
- }
- } else {
- delete state[good._id]
- return { ...state }
- }
- }
- if (type === 'CART_CLEAR') {
- return {}
- }
- return state
- }
- // акшоны для cartReducer
- // Добавление товара.Должен добавлять новый ключ в state, или обновлять, если ключа в state ранее не было, увеличивая количество
- const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', good, count })
- // Уменьшение количества товара.Должен уменьшать количество товара в state, или удалять его если количество будет 0 или отрицательным
- const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good })
- // Удаление товара.Должен удалять ключ из state
- const actionCartDel = (good) => ({ type: 'CART_DEL', good })
- // Задание количества товара.В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху(или создает новый ключ, если в корзине товара не было).Если count 0 или отрицательное число - удаляем ключ из корзины;
- const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good })
- // Очистка корзины.state должен стать пустым объектом { }
- const actionCartClear = () => ({ type: 'CART_CLEAR' })
- // объект со всеми редьюсерами
- const reducers = {
- promise: localStoredReducer(promiseReducer, 'promise'),
- auth: localStoredReducer(authReducer, 'auth'),
- cart: localStoredReducer(cartReducer, 'cart'),
- }
- const totalReducer = combineReducers(reducers)
- const store = createStore(totalReducer)
- store.subscribe(() => console.log(store.getState())) // для контроля выводим все изменения в магазине в консоль
- // GraphQL запросы переписанные сразу в акшоны ========================================================================================================
- // Запрос на список корневых категорий (без родителей)
- const actionCategoryFind = () => actionPromise('CategoryFind', gql(`query baseCategory($searchVariablesCategory: String){
- CategoryFind(query: $searchVariablesCategory){
- _id name parent {
- _id
- name
- }
- }
- }`, {
- searchVariablesCategory: JSON.stringify([{ parent: null }])
- }))
- store.dispatch(actionCategoryFind()) // и сразу же все запустили, чтоб постоянно отрисовывалась
- // Запрос для получения одной категории с товарами и картинками
- const actionCategoryFindOne = _id => actionPromise('CategoryFindOne', gql(`query categoryFindOne($searchVariablesCategoryOne: String,) {
- CategoryFindOne(query: $searchVariablesCategoryOne){
- _id name
- goods{
- _id name description price
- images{
- url
- }
- }
- subCategories{
- _id name
- }
- }
- }`, {
- searchVariablesCategoryOne: JSON.stringify([{ _id }])
- }))
- // Запрос на получение товара с описанием и картинками
- const actionGoodFindOne = _id => actionPromise('GoodFindOne', gql(`query oneGoodWithImages($searchVariablesGoodOne: String) {
- GoodFindOne(query: $searchVariablesGoodOne){
- _id name price description images {
- url
- }
- }
- }`, {
- searchVariablesGoodOne: JSON.stringify([{ _id }])
- }))
- // Запрос на логин
- const actionLogin = (login, password) => actionPromise('login', gql(`query login($login: String, $password: String) {
- login(login: $login, password: $password)
- }`, {
- login,
- password
- }))
- // показываем ошибку при авторизации, если неправильный логин или пароль
- function mistakeLogin() {
- main.innerHTML = `<p style="color: red;">Вы ввели неправильный логин или пароль</p>
- <button id='refreshBtn'>Повторить попытку</button>`
- const refresh = document.getElementById('refreshBtn')
- refresh.addEventListener('click', () => {
- location.reload()
- })
- }
- // Запрос на логин и последующую логинизацию в authReduser (thunk)
- const actionFullLogin = (login, password) =>
- async dispatch => {
- const token = await dispatch(actionLogin(login, password))
- if (token != null) {
- if (typeof (token) === 'string') {
- dispatch(actionAuthLogin(token))
- localStorage.authToken = token
- }
- } else {
- mistakeLogin()
- }
- }
- // Запрос на регистрацию
- const actionUserCreate = (login, password) => actionPromise('UserRegistrate', gql(`mutation registration($login:String,$password:String ){
- UserUpsert(user:{
- login:$login, password:$password
- }){
- _id createdAt
- }
- }`, {
- login,
- password
- }))
- // показываем ошибку при регистрации, если логин занят
- function mistakeRegistration() {
- main.innerHTML = `<p style="color: red;">Этот логин занят. Повторите регистрацию с уникальным логином или <a href="#/login/">авторизуйтесь</a></p>
- <button id='refreshBtn'>Повторить попытку</button>`
- const refresh = document.getElementById('refreshBtn')
- refresh.addEventListener('click', () => {
- location.reload()
- })
- }
- // Запрос на регистрацию и сразу на авторизацию пользователя на странице (thunk)
- const actionFullUserCreate = (login, password) =>
- async dispatch => {
- try {
- const registration = await dispatch(actionUserCreate(login, password))
- if (registration._id !== 'null') {
- dispatch(actionFullLogin(login, password))
- }
- }
- catch (e) {
- mistakeRegistration()
- }
- }
- // Запрос истории заказов
- const actionOrderFind = () => actionPromise('OrderFind', gql(`query order($order: String){
- OrderFind(query: $order){
- createdAt total orderGoods{
- good {
- name price images {
- url
- }
- }
- total count
- }
- }
- }`, {
- order: JSON.stringify([{}])
- }))
- // запрос отправку заказа на сервер
- const actionOrder = (goods) => actionPromise('orderCreate', gql(`mutation myOrder($createOrder: OrderInput){
- OrderUpsert(order: $createOrder) {
- orderGoods{
- count good{
- _id
- }
- }
- }
- }`, {
- createOrder: { orderGoods: goods }
- }))
- // запрос отправку заказа на сервер с последующей очисткой корзины (thunk)
- const actionFillOrder = () =>
- async dispatch => {
- // создаем массив с параметрами для диспатча
- let arrWithGoods = []
- for (const [id, { count }] of Object.entries(store.getState().cart)) {
- arrWithGoods.push({ count: count, good: { _id: id } })
- }
- // оформляем заказ
- await dispatch(actionOrder(arrWithGoods))
- // и как задиспатчится заказ чистим корзину
- store.dispatch(actionCartClear())
- }
- // Модуль =========================================================================================================
- // отрисовка в main списка товаров из категории
- const drawGoods = () => {
- const [, route] = location.hash.split('/')
- if (route !== 'category') return
- const { status, payload, error } = store.getState().promise.CategoryFindOne
- if (status === 'PENDING') {
- main.innerHTML = `<img src='https://cdn.dribbble.com/users/63485/screenshots/1309731/infinite-gif-preloader.gif'/>`
- }
- if (status === 'FULFILLED') {
- const { _id, name, goods, subCategories } = payload // подумать, нужны ли ид и субкатегория (субкатегория может ыть нужна, чтобы отрисовать ее в )
- main.innerHTML = `<h1>Категория: ${name}</h1>`
- // рисуем блок для субкатегорий
- const drawSubCat = document.createElement('div')
- drawSubCat.id = 'drawSubCat'
- main.append(drawSubCat)
- // отрисовываем карточки категорий
- // тут проблема. в айфонах subCategories из-за этого вот так с проверкой
- if (subCategories != null) {
- for (const { _id, name } of subCategories) {
- drawSubCat.innerHTML += `<div class="goodListCart" id='drawSubCatBtn'>
- <h2>${name}</h2>
- <a href="#/category/${_id}">Подробнее</a>
- </div>`
- }
- }
- // рисуем блок для товаров в категории
- const goodsList = document.createElement('div')
- goodsList.id = 'goodList'
- main.append(goodsList)
- // отрисовываем карточку товара в разделе категорий
- if (goods != null) {
- for (const { name, description, images, price, _id } of goods) { // удалить description из запроса и из отрисовки
- let img = url.slice(0, -7) + images[0].url
- goodsList.innerHTML += `
- <div class="goodListCart">
- <img src ="${img}" >
- <h2>${name}</h2>
- <p>Цена: ${price} грн.</p>
- <a href="#/good/${_id}">Подробнее</a>
- </div>`
- }
- }
- }
- }
- store.subscribe(drawGoods)
- // отрисовка товара после нажатия на карточку товара
- const drawGoodOne = () => {
- const [, route] = location.hash.split('/')
- if (route !== 'good') return
- const { status, payload, error } = store.getState().promise.GoodFindOne
- if (status === 'PENDING') {
- main.innerHTML = `<img src='https://cdn.dribbble.com/users/63485/screenshots/1309731/infinite-gif-preloader.gif'/>`
- }
- if (status === 'FULFILLED') {
- const { _id, name, description, price, images } = payload
- let img = url.slice(0, -7) + images[0].url
- main.innerHTML = `<img src="${img}">
- <h1>${name}</h1>
- <h2>Цена: ${price} грн./шт.</h2>
- <h3>Описание:</h3>
- <p>${description}</p>
-
- <div id=''>
- <label for="lname">Количество товара</label><br>
- <input type="number" min="0" value="0" id="addToCartInput" name="lname"><br><br>
- <input id="addToCartBtn" type="submit" value="Добавить в корзину" disabled="true">
- </div>`
- // включаем кнопку "добавить товар", только если указали количество товара
- addToCartInput.addEventListener('change', () => {
- if (addToCartInput.value == 0) {
- addToCartBtn.disabled = true
- } else {
- addToCartBtn.disabled = false
- }
- // addToCartBtn.disabled = (addToCartInput.value == 0 || false) // или можно написать вот так эту проверку
- })
- // Добавляем товар в корзину по клику кнопки "добавить"
- addToCartBtn.addEventListener('click', () => {
- store.dispatch(actionCartAdd({ _id, name, description, price, img }, +addToCartInput.value))
- })
- }
- }
- store.subscribe(drawGoodOne)
- //отрисовка истории заказов пользователя
- const history = () => {
- const [, route] = location.hash.split('/')
- if (route !== 'history') return
- const { status, payload, error } = store.getState().promise.OrderFind
- if (status === 'PENDING') {
- main.innerHTML = `<img src='https://cdn.dribbble.com/users/63485/screenshots/1309731/infinite-gif-preloader.gif'/>`
- }
- if (status === 'FULFILLED') {
- main.innerHTML = `<h1>История заказов</h1>`
- for (const { createdAt, total, orderGoods } of payload) {
- const orderFindCart = document.createElement('div')
- orderFindCart.classList = 'orderFindCart'
- main.append(orderFindCart)
- // формируем дату создания поста
- const dateOfOrder = new Date(+createdAt)
- const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
- const dateOfOrderParse = `${dateOfOrder.getDate() < 10 ? '0' + dateOfOrder.getDate() : dateOfOrder.getDate()} ${months[dateOfOrder.getMonth()]} ${dateOfOrder.getFullYear()} ${dateOfOrder.getHours()}:${dateOfOrder.getMinutes() < 10 ? '0' + dateOfOrder.getMinutes() : dateOfOrder.getMinutes()}`
- orderFindCart.innerHTML = `
- <p>Создано: ${dateOfOrderParse}</p>
- <h2>Сумма заказа: ${total} тугриков</h2>`
- for (const { good: { name, price, images }, count, total } of orderGoods) {
- let img = url.slice(0, -7) + images[0].url
- orderFindCart.innerHTML += `<div class="orderFindCart">
- <img src ="${img}">
- <div class="orderFindCartDescription">
- <p>Товар: ${name}</p>
- <p>Цена товара: ${price}</p>
- <p>Количество товара в заказе: ${count}</p>
- <p>Итоговая сумма: ${total}</p>
- </div>
- </div>`
- }
- }
- }
- }
- store.subscribe(history)
- // отрисовываем левое меню
- const leftMenu = () => {
- const { status, payload, error } = store.getState().promise.CategoryFind
- if (status === 'FULFILLED' && payload) {
- aside.innerHTML = ''
- aside.innerHTML = `<a href="/">Главная</a>`
- for (const { _id, name } of payload) {
- aside.innerHTML += `<a href="#/category/${_id}">${name}</a>`
- }
- }
- }
- store.subscribe(leftMenu)
- // изменение цифры количества товаров в корзине (в красном лейбле)
- const redLabel = () => {
- let goodCount = 0
- for (let { count } of Object.values(store.getState().cart)) {
- goodCount += count
- }
- document.getElementById('cartIcon').textContent = goodCount
- }
- store.subscribe(redLabel)
- // подписчик для отображения залогинен или нет пользователь, а также для отображения страниц регистрации и логинизации в шапке
- const userStatus = () => {
- if (!(store.getState().auth.payload?.sub?.login)) {
- login.innerHTML = `<a href ="#/login/">Авторизоваться</a></br>
- <a href ="#/register/">Зарегистрироваться</a></br>`
- } else {
- login.innerHTML = `Привет, ${store.getState().auth.payload?.sub?.login}</br>
- <a href="#/history/">История заказов</a></br>
- <a href="" id="logout">Выйти</a>`
- logout.onclick = () => {
- store.dispatch(actionAuthLogout())
- }
- }
- }
- store.subscribe(userStatus)
- // функция для страницы корзины. из-за тго, что очень массивная страница с большим функционалом, отрисовывааем ее отдельно
- function cartPage() {
- const [, route] = location.hash.split('/')
- if (route !== 'cart') return
- if (Object.keys(store.getState().cart).length !== 0) {
- main.innerHTML = '<h1>Корзина</h1>'
- // подсчитываем сумму всего заказа (изначально при входе в корзину)
- let summ = 0
- for (const { count, good: { price } } of Object.values(store.getState().cart)) {
- summ += (count * price)
- }
- // создаем с помощью цикла карточки товаров в заказе
- for (const [id, { count, good: { description, img, name, price, _id } }] of Object.entries(store.getState().cart)) {
- const goodInCart = document.createElement('div')
- goodInCart.id = "goodInCart"
- goodInCart.innerHTML = `<h2>${name}</h2>`
- main.append(goodInCart)
- const goodInCartContent = document.createElement('div')
- goodInCartContent.id = 'goodInCartContent'
- goodInCart.append(goodInCartContent)
- const goodInCartIimg = document.createElement('div')
- goodInCartIimg.id = 'goodInCartIimg'
- goodInCartIimg.innerHTML = `<img src="${img}">`
- goodInCartContent.append(goodInCartIimg)
- const goodInCartIDescription = document.createElement('div')
- goodInCartIDescription.id = 'goodInCartIDescription'
- goodInCartContent.append(goodInCartIDescription)
- // рисуем кнопки на карточке товара
- //кнопка удаления
- const delBtn = document.createElement('button')
- delBtn.innerHTML = '-'
- goodInCartIDescription.append(delBtn)
- const goodInCartValue = document.createElement('input')
- goodInCartValue.id = 'goodInCartValue'
- goodInCartValue.value = count
- goodInCartIDescription.append(goodInCartValue)
- // кнопка добавления
- const addBtn = document.createElement('button')
- addBtn.innerHTML = '+'
- goodInCartIDescription.append(addBtn)
- // создаем блок с кнопками управления
- const blockWithButtons = document.createElement('div')
- goodInCartIDescription.append(blockWithButtons)
- //кнопка универсального добавления товара
- const cartSetBtn = document.createElement('button')
- cartSetBtn.innerHTML = 'Добавить товар'
- blockWithButtons.append(cartSetBtn)
- // кнопка удаления товара из корзины
- const cartDelBtn = document.createElement('button')
- cartDelBtn.innerHTML = 'Удалить товар'
- blockWithButtons.append(cartDelBtn)
- const goodInCartAbout = document.createElement('p')
- goodInCartAbout.innerHTML = `Описание: ${description}.</br></br>Цена за ед. ${price} тугриков.`
- goodInCartIDescription.append(goodInCartAbout)
- const goodInCartFullPrice = document.createElement('p')
- goodInCartFullPrice.innerHTML = `Итоговая стоимость за товар: ${count * price} тугриков`
- // goodInCartFullPrice.innerHTML = `Итоговая стоимость за товар: ${store.getState().cart[_id].count * price} тугриков`
- goodInCartIDescription.append(goodInCartFullPrice)
- // блок колбеков на кнопках
- // коллбек на кнопку +
- addBtn.addEventListener('click', async () => {
- await store.dispatch(actionCartAdd({ _id, name, description, price, img }))
- const newCount = store.getState().cart[_id].count
- goodInCartValue.value = newCount
- // изменяем стоимость на карточке товара
- goodInCartFullPrice.innerHTML = `Итоговая стоимость за товар: ${newCount * price} тугриков`
- })
- // коллбек на кнопку -
- delBtn.addEventListener('click', async () => {
- await store.dispatch(actionCartSub({ _id, name, description, price, img }))
- // прверяем, если после этого товара не осталось (удалили последний товар), перерисовываем страницу. если товар еще есть в заказе, просто понимажем его счетчик
- if (store.getState().cart[_id]) {
- const newCount = store.getState().cart[_id].count
- goodInCartValue.value = newCount
- // изменяем стоимость карточки
- goodInCartFullPrice.innerHTML = `Итоговая стоимость за товар: ${newCount * price} тугриков`
- } else {
- cartPage()
- }
- })
- // коллбек на кнопку "Добавить товар"
- cartSetBtn.addEventListener('click', async () => {
- const newCount = goodInCartValue.value
- // проверяем число на 0 или больше
- if (newCount > 0) {
- await store.dispatch(actionCartSet({ _id, name, description, price, img }, +newCount))
- // изменяем стоимость карточки
- goodInCartFullPrice.innerHTML = `Итоговая стоимость за товар: ${newCount * price} тугриков`
- // изменяем общую сумму заказа
- // cartOrderFullPrice.innerHTML = `Сумма заказа: ${summ} тугриков`
- } else {
- await store.dispatch(actionCartDel({ _id }))
- // и сразу обновляем страницу корзины
- cartPage()
- }
- })
- // коллбек на кнопку "Удалить товар"
- cartDelBtn.addEventListener('click', async () => {
- await store.dispatch(actionCartDel({ _id }))
- // и сразу обновляем страницу корзины
- cartPage()
- })
- }
- // отрисовываем блок под карточками товаров с общей стоимостью покупки и кнопками "Оформить заказ" и "Очистить корзину"
- const cartOrderFullPrice = document.createElement('p')
- cartOrderFullPrice.innerHTML = `Общая сумма заказа: ${summ} тугриков`
- main.append(cartOrderFullPrice)
- const cartOrderCreate = document.createElement('button')
- cartOrderCreate.id = 'cartOrderCreateBtn'
- cartOrderCreate.innerHTML = 'Оформить заказ'
- main.append(cartOrderCreate)
- const breakLine = document.createElement('br')
- main.append(breakLine)
- const cartClean = document.createElement('button')
- cartClean.innerHTML = 'Очистить корзину'
- main.append(cartClean)
- // коллбек на кнопку очистки корзины
- cartClean.addEventListener('click', async () => {
- await store.dispatch(actionCartClear())
- // и сразу обновляем страницу корзины
- cartPage()
- })
- // коллбек на кнопку оформления заказа
- cartOrderCreate.addEventListener('click', async () => {
- await store.dispatch(actionFillOrder())
- // и сразу обновляем страницу корзины
- cartPage()
- })
- // подписчик для изменения отображения на странице корзины
- const summFinally = () => {
- // просчитываем полную сумму заказа
- let summ = 0
- for (const { count, good: { price } } of Object.values(store.getState().cart)) {
- summ += (count * price)
- }
- // меняем общую стоимость заказа при изменении стора
- cartOrderFullPrice.innerHTML = `Сумма заказа: ${summ} тугриков`
- }
- store.subscribe(summFinally)
- } else {
- main.innerHTML = `<p>Ваша корзина пуста.Чтобы сделать заказ, сначала добавьте товары.</p>`
- }
- }
- // функция - конструктор формы логин/пароль =========================================================================================================
- function LoginForm(parent) {
- function Password(parent, open) {
- // отображение формы для пароля
- const inputPassword = document.createElement('input')
- inputPassword.type = 'password'
- inputPassword.placeholder = 'Insert password'
- parent.append(inputPassword)
- // создание и отображение чекбокса (открыть/скрыть пароль)
- const inputCheckbox = document.createElement('input')
- inputCheckbox.type = 'checkbox'
- inputCheckbox.checked = false
- parent.append(inputCheckbox)
- // создаем геттеры
- this.getValue = () => inputPassword.value
- this.getOpen = () => inputCheckbox.checked
- // создаем сеттеры
- this.setValue = (value) => inputPassword.value = value
- this.setOpen = (open) => {
- if (open === true) {
- inputPassword.type = 'text'
- inputCheckbox.checked = true
- }
- if (open === false) {
- inputPassword.type = 'password'
- inputCheckbox.checked = false
- }
- return inputPassword.type, inputCheckbox.checked
- }
- // starting onChange
- inputPassword.addEventListener('input', () => {
- this.onChange(inputPassword.value)
- })
- // starting onOpenChange + change inputPasswor hide/see
- inputCheckbox.addEventListener('change', () => {
- this.setOpen(inputCheckbox.checked)
- this.onOpenChange(inputCheckbox.checked)
- })
- }
- function Login(parent) {
- // создаем и отрысовываем поле для ввода логина
- const inputLogin = document.createElement('input')
- inputLogin.type = 'text'
- inputLogin.placeholder = 'Insert login'
- parent.append(inputLogin)
- // создаем геттеры
- this.getValue = () => inputLogin.value
- // создаем сеттеры
- this.setValue = (value) => inputLogin.value = value
- // starting onChange
- inputLogin.addEventListener('input', () => {
- this.onChange(inputLogin.value)
- })
- }
- // создание и отрисовывание формы для логина/пароля
- // const form = document.createElement('form') // если формируем форму, а не обычный див, тогда не работает запрос и появляется в адресной строке "?" после домена в урле
- const form = document.createElement('div')
- parent.append(form)
- const loginLabel = document.createElement('label')
- loginLabel.innerText = 'Login:'
- form.append(loginLabel)
- let breakSymbol = document.createElement('br')
- form.append(breakSymbol)
- // отрисовываем поле логина
- const login = new Login(form)
- breakSymbol = document.createElement('br')
- form.append(breakSymbol)
- const passwordLabel = document.createElement('label')
- passwordLabel.innerText = 'Password:'
- form.append(passwordLabel)
- breakSymbol = document.createElement('br')
- form.append(breakSymbol)
- // отрисовываем поле для пароля
- const password = new Password(form, true)
- breakSymbol = document.createElement('br')
- form.append(breakSymbol)
- // создание и отрисовывание кнопки для входа
- const confirmBtn = document.createElement('button')
- confirmBtn.innerText = 'Confirm'
- confirmBtn.id = 'confirmBtn'
- confirmBtn.type = 'submit'
- confirmBtn.style.marginTop = '10px'
- confirmBtn.disabled = true
- form.append(confirmBtn)
- // change confirmBtn.disabled
- function checkButton() {
- if (login.getValue() !== '' && password.getValue() !== '') {
- confirmBtn.disabled = false
- } else {
- confirmBtn.disabled = true
- }
- return confirmBtn.disabled
- }
- checkButton()
- // listening password and login
- login.onChange = password.onChange = checkButton
- // create getters
- this.getPasswordValue = () => password.getValue()
- this.getPasswordOpen = () => password.getOpen()
- this.getLoginValue = () => login.getValue()
- this.getButtonStatus = () => confirmBtn.disabled
- // create setters
- this.setPasswordValue = (value) => password.setValue(value)
- this.setLoginValue = (value) => login.setValue(value)
- this.setPasswordOpen = (status) => password.setOpen(status)
- // create callbacks
- this.onOpenChange = open => password.onOpenChange = open
- }
- // проверяем, что сейчас находится в урле и, исходя из приставки в урле, запускаем нужную функцию
- window.onhashchange = () => {
- const [, route, _id] = location.hash.split('/')
- const routes = {
- category() {
- store.dispatch(actionCategoryFindOne(_id))
- },
- good() {
- store.dispatch(actionGoodFindOne(_id))
- },
- login() {
- if (!localStorage.authToken) { // если пользователь залогинен, то ему больше не рисовать форму логина
- main.innerHTML = '<h2>Авторизуйтесь</h2>'
- //нарисовать форму логина, которая по нажатию кнопки Login делает store.dispatch(actionFullLogin(login, password))
- const logForm = new LoginForm(main)
- confirmBtn.addEventListener('click', async () => {
- localStorage.removeItem('authToken') // на всякий случай (а нужно ли?) удаляю старый authToken перед регистрацией
- await store.dispatch(actionFullLogin(logForm.getLoginValue(), logForm.getPasswordValue()))
- // if (store.getState().promise.login.payload != null) {
- if ((Object.keys(store.getState().auth)).length) {
- main.innerHTML = `<h2>С возвращением, ${logForm.getLoginValue()}!<h2>`
- }
- })
- }
- },
- register() {
- if (!localStorage.authToken) { // если пользователь залогинен, то ему больше не отображать страницу регистрации (не рисовать форму)
- main.innerHTML = `<h2>Зарегистрируйтесь</h2>`
- // Запрос на регистрацию. Страница # / register /.В onhashchange НЕ происходит dispatch, вместо этого рисуется форма логина из предыдущих ДЗ, по логину же происходит dispatch
- const regForm = new LoginForm(main)
- confirmBtn.addEventListener('click', async () => {
- localStorage.removeItem('authToken') // на всякий случай (а нужно ли?) удаляю старый authToken перед регистрацией
- await store.dispatch(actionFullUserCreate(regForm.getLoginValue(), regForm.getPasswordValue()))
- if ((Object.keys(store.getState().auth)).length) {
- main.innerHTML = `<h2>Поздравляем, регистраций прошла успешно!<h2>`
- }
- })
- }
- },
- history() {
- store.dispatch(actionOrderFind())
- },
- cart() {
- if (localStorage.authToken) { // если пользователь авторизован - показываем корзину, если нет - предлагаем авторизоваться!
- cartPage()
- } else {
- main.innerHTML = `<h1> Корзина</h1>
- <p>Для просмотра корзины Вам необходимо авторизоваться!</p>
- <a href="#/login/">Авторизация</a>`
- }
- }
- }
- if (route in routes) {
- routes[route]()
- }
- }
- window.onhashchange()
- // 3. разобраться с тем, почему корзина хуйней страдает
- // 6. сonst form = document.createElement('form') // если формируем форму, а не обычный див на логине, тогда не работает запрос и появляется в адресной строке "?" после домена в урле
|