|
@@ -0,0 +1,655 @@
|
|
|
+// 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
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ----------------------------------------------------------
|
|
|
+
|
|
|
+// decode token
|
|
|
+
|
|
|
+function jwtDecode(token) {
|
|
|
+ try {
|
|
|
+ return JSON.parse(atob(token.split('.')[1]))
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ----------------------------------------------------------
|
|
|
+
|
|
|
+// reducers
|
|
|
+
|
|
|
+function authReducer(state = {}, { type, token }) { //authorise reducer
|
|
|
+ if (state === undefined) {
|
|
|
+ if (localStorage.authToken) {
|
|
|
+ type = "AUTH_LOGIN";
|
|
|
+ token = localStorage.authToken;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'AUTH_LOGIN') { //то мы логинимся
|
|
|
+ const payload = jwtDecode(token)
|
|
|
+ if (payload) {
|
|
|
+ return {
|
|
|
+ token,
|
|
|
+ payload
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'AUTH_LOGOUT') { //мы разлогиниваемся
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ return state
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function promiseReducer(state = {}, { type, name, status, payload, error }) { //promise reducer
|
|
|
+ if (type === 'PROMISE') {
|
|
|
+ return {
|
|
|
+ ...state,
|
|
|
+ [name]: { status, payload, error }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return state
|
|
|
+}
|
|
|
+
|
|
|
+function cartReducer(state = {}, { type, good, count = 1 }) {
|
|
|
+ if (type === 'CART_ADD') {
|
|
|
+ return {
|
|
|
+ ...state,
|
|
|
+ [good._id]: { count: (state[good._id]?.count || 0) + count, good: good }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'CART_CHANGE') {
|
|
|
+ return {
|
|
|
+ ...state,
|
|
|
+ [good._id]: { count, good }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'CART_DELETE') {
|
|
|
+ delete state[good._id]
|
|
|
+ return {
|
|
|
+ ...state,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'CART_CLEAR') {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ return state
|
|
|
+}
|
|
|
+
|
|
|
+const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', good, count })
|
|
|
+const actionCartChange = (good, count = 1) => ({ type: 'CART_CHANGE', good, count })
|
|
|
+const actionCartDelete = (good) => ({ type: 'CART_DELETE', good })
|
|
|
+const actionCartClear = () => ({ type: 'CART_CLEAR' })
|
|
|
+
|
|
|
+function combineReducers(reducers) {
|
|
|
+ function combinedReducer(combinedState = {}, action) {
|
|
|
+ const newCombinedState = {}
|
|
|
+ for (const [reducerName, reducer] of Object.entries(reducers)) {
|
|
|
+ const newSubState = reducer(combinedState[reducerName], action)
|
|
|
+ if (newSubState !== combinedState[reducerName]) {
|
|
|
+ newCombinedState[reducerName] = newSubState
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (Object.keys(newCombinedState).length === 0) {
|
|
|
+ return combinedState
|
|
|
+ }
|
|
|
+ return { ...combinedState, ...newCombinedState }
|
|
|
+ }
|
|
|
+
|
|
|
+ return combinedReducer
|
|
|
+}
|
|
|
+
|
|
|
+const store = createStore(combineReducers({ promise: promiseReducer, auth: authReducer, cart: cartReducer }))
|
|
|
+// store.subscribe(() => console.log(store.getState()))
|
|
|
+
|
|
|
+// ----------------------------------------------------------
|
|
|
+
|
|
|
+// GQL
|
|
|
+
|
|
|
+const getGQL = url =>
|
|
|
+ (query, variables) => fetch(url, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
|
|
|
+ },
|
|
|
+ body: JSON.stringify({ query, variables })
|
|
|
+ }).then(res => res.json())
|
|
|
+ .then(data => {
|
|
|
+ if (data.data) {
|
|
|
+ return Object.values(data.data)[0]
|
|
|
+ }
|
|
|
+ else throw new Error(JSON.stringify(data.errors))
|
|
|
+ })
|
|
|
+
|
|
|
+const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/'
|
|
|
+
|
|
|
+const gql = getGQL(backendURL + 'graphql')
|
|
|
+
|
|
|
+// ----------------------------------------------------------
|
|
|
+
|
|
|
+// promises
|
|
|
+
|
|
|
+const actionPromise = (name, promise) =>
|
|
|
+ async dispatch => {
|
|
|
+ dispatch(actionPending(name))
|
|
|
+ try {
|
|
|
+ let payload = await promise
|
|
|
+ dispatch(actionFulfilled(name, payload))
|
|
|
+ return payload
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ dispatch(actionRejected(name, e))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+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 })
|
|
|
+
|
|
|
+// ----------------------------------------------------------
|
|
|
+
|
|
|
+// actions
|
|
|
+
|
|
|
+const actionFullRegister = (login, password) =>
|
|
|
+ actionPromise('fullRegister', gql(`mutation UserUpsert($login: String, $password: String){UserUpsert(user: {login:$login,password:$password}){_id}}`, { login: login, password: password }))
|
|
|
+
|
|
|
+
|
|
|
+const actionAuthLogin = (token) =>
|
|
|
+ (dispatch, getState) => {
|
|
|
+ const oldState = getState()
|
|
|
+ dispatch({ type: 'AUTH_LOGIN', token })
|
|
|
+ const newState = getState()
|
|
|
+ if (oldState !== newState)
|
|
|
+ localStorage.authToken = token
|
|
|
+ }
|
|
|
+
|
|
|
+const actionAuthLogout = () =>
|
|
|
+ dispatch => {
|
|
|
+ dispatch({ type: 'AUTH_LOGOUT' })
|
|
|
+ localStorage.removeItem('authToken')
|
|
|
+ }
|
|
|
+
|
|
|
+const actionFullLogin = (login, password) =>
|
|
|
+ actionPromise('fullLogin', gql(`query login($login:String,$password:String){login(login:$login,password:$password)}`, { login: login, password: password }))
|
|
|
+
|
|
|
+
|
|
|
+const orderFind = () =>
|
|
|
+ actionPromise('orderFind', gql(`query orderFind{
|
|
|
+ OrderFind(query: "[{}]"){
|
|
|
+ _id createdAt total orderGoods {_id price count good{name price images{url}}}
|
|
|
+ }
|
|
|
+}`, { q: JSON.stringify([{}]) }))
|
|
|
+
|
|
|
+const actionAddOrder = (cart) =>
|
|
|
+ actionPromise('actionAddOrder', gql(`mutation newOrder($cart: [OrderGoodInput])
|
|
|
+{OrderUpsert(order: {orderGoods: $cart})
|
|
|
+{_id total}}`, { cart: cart }))
|
|
|
+
|
|
|
+const actionRootCats = () =>
|
|
|
+ actionPromise('rootCats', gql(`query {
|
|
|
+ CategoryFind(query: "[{\\"parent\\":null}]"){
|
|
|
+ _id name
|
|
|
+
|
|
|
+ }
|
|
|
+ }`))
|
|
|
+
|
|
|
+const actionCatById = (_id) => // √
|
|
|
+ actionPromise('catById', gql(`query catById($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 goodById($q: String){
|
|
|
+ GoodFindOne(query: $q){
|
|
|
+ _id name price description images {
|
|
|
+ url
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }`, { q: JSON.stringify([{ _id }]) }))
|
|
|
+
|
|
|
+store.dispatch(actionRootCats())
|
|
|
+
|
|
|
+// ----------------------------------------------------------
|
|
|
+
|
|
|
+// subscribe
|
|
|
+
|
|
|
+store.subscribe(() => {
|
|
|
+
|
|
|
+ const { rootCats } = (store.getState()).promise
|
|
|
+ if (rootCats?.payload) {
|
|
|
+ cat.innerHTML = `<li class="list-group-item"><b>Categories</b></li>`
|
|
|
+
|
|
|
+ for (const { _id, name } of rootCats?.payload) {
|
|
|
+ const categories = document.createElement('li')
|
|
|
+ categories.innerHTML = `<a href='#/category/${_id}'>${name}</a>`
|
|
|
+ categories.style = ' padding-left: 30px ; '
|
|
|
+
|
|
|
+ cat.append(categories)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (!rootCats) {
|
|
|
+ cat.innerHTML = '<img src = img/preloader.gif>'
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+store.subscribe(() => {
|
|
|
+ const { catById } = (store.getState()).promise
|
|
|
+ const [, route, _id] = location.hash.split('/')
|
|
|
+ if (catById?.payload && route === 'category') {
|
|
|
+
|
|
|
+ main.innerHTML = ``
|
|
|
+ const { name } = catById.payload
|
|
|
+
|
|
|
+ const card = document.createElement('div')
|
|
|
+ card.id = 'sub-card'
|
|
|
+ card.innerHTML = `<h2 id="cat-name"><b>${name}</b></h2><br>`
|
|
|
+
|
|
|
+ const backPart = document.createElement('div')
|
|
|
+ backPart.id = 'back'
|
|
|
+ const backBtn = document.createElement('button')
|
|
|
+ backBtn.setAttribute('type', 'button');
|
|
|
+ backBtn.addEventListener('click', () => {
|
|
|
+ history.back();
|
|
|
+ });
|
|
|
+ backBtn.innerText = '⬅'
|
|
|
+ backPart.appendChild(backBtn)
|
|
|
+
|
|
|
+
|
|
|
+ if (catById.payload.subCategories) {
|
|
|
+ for (const { _id, name } of catById.payload?.subCategories) {
|
|
|
+ card.innerHTML += `<a href='#/category/${_id}' class='subcategories'>${name}</a>`
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // card.append(backPart)
|
|
|
+ main.append(card, backPart)
|
|
|
+ for (const { _id, name, price, images } of catById.payload?.goods) {
|
|
|
+ const card = document.createElement('div')
|
|
|
+ card.id = 'card'
|
|
|
+ card.innerHTML = `<h5><b>${name}</b></h5>
|
|
|
+ <div class='card-img'><img src="http://shop-roles.node.ed.asmer.org.ua/${images[0].url}"/></div><br>
|
|
|
+ <h2 style='color: green; text-align:center'>Price: $${price}</h2><br><br>
|
|
|
+ <a class="" style="width: 100%;" href='#/good/${_id}'>More details -> </a><br><br>`
|
|
|
+ let button = document.createElement('button')
|
|
|
+ button.innerText = 'BUY'
|
|
|
+ button.className = 'buy-btn'
|
|
|
+ button.setAttribute('type', 'button');
|
|
|
+ button.onclick = async () => {
|
|
|
+ await store.dispatch(actionCartAdd({ _id: _id, name: name, price: price, images: images }))
|
|
|
+ console.log('hi')
|
|
|
+ }
|
|
|
+ card.append(button)
|
|
|
+ main.append(card)
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+store.subscribe(() => {
|
|
|
+ const { goodById } = (store.getState()).promise
|
|
|
+ const [, route, _id] = location.hash.split('/')
|
|
|
+ if (goodById?.payload && route === 'good') {
|
|
|
+ const { _id, name, description, images, price } = goodById.payload
|
|
|
+ main.innerHTML = `<h1>${name}</h1>`
|
|
|
+
|
|
|
+ const card = document.createElement('div')
|
|
|
+ card.id = 'desc-card'
|
|
|
+
|
|
|
+ const backPart = document.createElement('div')
|
|
|
+ backPart.id = 'back'
|
|
|
+
|
|
|
+ const backBtn = document.createElement('button')
|
|
|
+ backBtn.setAttribute('type', 'button');
|
|
|
+ backBtn.addEventListener('click', () => {
|
|
|
+ history.back();
|
|
|
+ });
|
|
|
+ backBtn.innerText = '⬅'
|
|
|
+ backPart.appendChild(backBtn)
|
|
|
+
|
|
|
+ let block = document.createElement('div')
|
|
|
+ block.id = 'price'
|
|
|
+ block.innerHTML = `<h2>Price: <b class='price'>$${price}</b></h2>`
|
|
|
+ let button = document.createElement('button')
|
|
|
+ button.innerText = 'BUY'
|
|
|
+ button.className = 'buy-btn'
|
|
|
+ button.setAttribute('type', 'button');
|
|
|
+ button.style = 'height:80px'
|
|
|
+ button.onclick = async () => {
|
|
|
+ await store.dispatch(actionCartAdd({ _id: _id, name: name, price: price, images: images }))
|
|
|
+ console.log('hi')
|
|
|
+ }
|
|
|
+
|
|
|
+ card.innerHTML = `<img src="http://shop-roles.node.ed.asmer.org.ua/${images[0].url}" /><br><br>`
|
|
|
+ card.append(block)
|
|
|
+ card.innerHTML += `<p><b>Description:</b> ${description}</p>`
|
|
|
+ main.append(backPart, card, button)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+store.subscribe(() => {
|
|
|
+ const { orderFind } = (store.getState()).promise
|
|
|
+ const [, route, _id] = location.hash.split('/')
|
|
|
+
|
|
|
+ if (orderFind?.payload && route === 'orderFind') {
|
|
|
+ main.innerHTML = '<h1>ORDER HISTORY</h1>'
|
|
|
+
|
|
|
+ for (const { _id, createdAt, total, orderGoods } of orderFind.payload.reverse()) {
|
|
|
+ const card = document.createElement('div')
|
|
|
+ card.className = 'order-card'
|
|
|
+ card.innerHTML = `<h3>Order: ${createdAt}</h3>`
|
|
|
+ for (const { count, good } of orderGoods) {
|
|
|
+
|
|
|
+ const divGood = document.createElement('div')
|
|
|
+ divGood.style = "display:flex;margin-bottom: 20px;"
|
|
|
+
|
|
|
+ divGood.innerHTML += `<div><b>${good.name}</b><br> Price: <b>$${good.price}</b><br> Amount: <b>${count} pt</b></b></div><img style="max-width: 80px;margin-right: 20px;display: block;margin-left: auto;" src="http://shop-roles.node.ed.asmer.org.ua/${good.images[0].url}"/><br><br>`
|
|
|
+ card.append(divGood)
|
|
|
+ }
|
|
|
+ card.innerHTML += 'Date: <b>' + new Date(+createdAt).toLocaleString().replace(/\//g, '.') + '</b>'
|
|
|
+ card.innerHTML += `<br><h2>Total: <b style="color:green;">$${total}</b></h2>`
|
|
|
+ main.append(card)
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// ----------------------------------------------------------
|
|
|
+
|
|
|
+// window
|
|
|
+
|
|
|
+function display() {
|
|
|
+ let token = localStorage.authToken
|
|
|
+ if (token) {
|
|
|
+ form_yes.style.display = 'flex'
|
|
|
+ form_no.style.display = 'none'
|
|
|
+ UserNick.innerText = JSON.parse(window.atob(localStorage.authToken.split('.')[1])).sub.login
|
|
|
+ } else {
|
|
|
+ form_yes.style.display = 'none'
|
|
|
+ form_no.style.display = 'flex'
|
|
|
+ }
|
|
|
+}
|
|
|
+display()
|
|
|
+
|
|
|
+window.onhashchange = () => {
|
|
|
+ const [, route, _id] = location.hash.split('/')
|
|
|
+
|
|
|
+ const routes = {
|
|
|
+ category() {
|
|
|
+ store.dispatch(actionCatById(_id))
|
|
|
+ },
|
|
|
+
|
|
|
+ good() {
|
|
|
+ store.dispatch(actionGoodById(_id))
|
|
|
+ },
|
|
|
+
|
|
|
+ login() {
|
|
|
+ main.innerHTML = ''
|
|
|
+ let form = document.createElement('div')
|
|
|
+ let div1 = document.createElement('div')
|
|
|
+ let div2 = document.createElement('div')
|
|
|
+
|
|
|
+ div1.innerHTML = `<h1>LOGIN</h1>`
|
|
|
+ let loginInput = document.createElement('input')
|
|
|
+ loginInput.placeholder = 'Type your username'
|
|
|
+ loginInput.name = 'login'
|
|
|
+ div1.style.display = 'flex'
|
|
|
+ div1.style.flexDirection = 'column'
|
|
|
+ let passwordInput = document.createElement('input')
|
|
|
+ passwordInput.placeholder = 'Type your password'
|
|
|
+ passwordInput.name = 'password'
|
|
|
+ passwordInput.type = 'password';
|
|
|
+ div1.append(loginInput)
|
|
|
+ div2.append(passwordInput)
|
|
|
+
|
|
|
+ let button = document.createElement('button')
|
|
|
+ button.innerText = "LOGIN"
|
|
|
+ button.id = 'login-btn'
|
|
|
+ button.setAttribute('type', 'button');
|
|
|
+
|
|
|
+ button.onclick = async () => {
|
|
|
+
|
|
|
+ let tokenPromise = async () => await store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))
|
|
|
+ let token = await tokenPromise()
|
|
|
+
|
|
|
+ if (token !== null) {
|
|
|
+ store.dispatch(actionAuthLogin(token))
|
|
|
+ display()
|
|
|
+ document.location.href = "#/orderFind";
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ loginInput.value = ''
|
|
|
+ passwordInput.value = ''
|
|
|
+ alert("Incorrect username or password.")
|
|
|
+ store.dispatch(actionAuthLogout())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ form.append(div1, div2, button)
|
|
|
+ main.append(form)
|
|
|
+ },
|
|
|
+
|
|
|
+ register() {
|
|
|
+ main.innerHTML = ''
|
|
|
+ let form = document.createElement('div')
|
|
|
+ let div1 = document.createElement('div')
|
|
|
+ let div2 = document.createElement('div')
|
|
|
+
|
|
|
+ div1.innerHTML += `<h1>REGISTER</h1>`
|
|
|
+ let loginInput = document.createElement('input')
|
|
|
+ loginInput.placeholder = "Type your username"
|
|
|
+ div1.append(loginInput)
|
|
|
+
|
|
|
+ let passwordInput = document.createElement('input')
|
|
|
+ passwordInput.placeholder = "Type your password"
|
|
|
+ passwordInput.type = 'password'
|
|
|
+
|
|
|
+ div2.append(passwordInput)
|
|
|
+
|
|
|
+ let button = document.createElement('button')
|
|
|
+ button.innerText = "CREATE ACCOUNT"
|
|
|
+ button.id = 'reg-btn'
|
|
|
+ button.setAttribute('type', 'button');
|
|
|
+
|
|
|
+ let textAlert = document.createElement('div')
|
|
|
+ let textAlert2 = document.createElement('div')
|
|
|
+
|
|
|
+ let putInText = "Username and password required!"
|
|
|
+ let userAlready = "An account with this username already exist!"
|
|
|
+ textAlert.append(userAlready)
|
|
|
+ textAlert2.append(putInText)
|
|
|
+
|
|
|
+ textAlert2.style = 'display : none; color : red'
|
|
|
+ textAlert.style = 'display : none; color : red'
|
|
|
+
|
|
|
+
|
|
|
+ button.onclick = async () => {
|
|
|
+ let register = await store.dispatch(actionFullRegister(loginInput.value, passwordInput.value))
|
|
|
+ let tokenPromise = async () => await store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))
|
|
|
+
|
|
|
+ if (loginInput.value == '' || passwordInput.value == '') {
|
|
|
+ textAlert2.style.display = 'block'
|
|
|
+
|
|
|
+ } else {
|
|
|
+ if (register !== null) {
|
|
|
+ let token = await tokenPromise()
|
|
|
+ store.dispatch(actionAuthLogin(token))
|
|
|
+ // console.log(token)
|
|
|
+ display()
|
|
|
+ document.location.href = "#/orderFind";
|
|
|
+
|
|
|
+ } else {
|
|
|
+ textAlert.style.display = 'block'
|
|
|
+ textAlert2.style.display = 'none'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ form.append(div1, div2, button)
|
|
|
+ form.append(textAlert, textAlert2)
|
|
|
+ main.append(form)
|
|
|
+ },
|
|
|
+
|
|
|
+ orderFind() {
|
|
|
+ store.dispatch(orderFind())
|
|
|
+ },
|
|
|
+
|
|
|
+ cart() {
|
|
|
+ main.innerHTML = '<h1>CART</h1>'
|
|
|
+
|
|
|
+ for (const [_id, obj] of Object.entries(store.getState().cart)) {
|
|
|
+ const card = document.createElement('div')
|
|
|
+ card.style = 'width: 33.33%;border-style: groove;border-color: #ced4da17;padding: 10px;border-radius: 10px;margin: 5px;display: flex; flex-direction: column ; align-items: center ; justify-content: space-between'
|
|
|
+
|
|
|
+ const { count, good } = obj
|
|
|
+
|
|
|
+ card.innerHTML += `Products: <b>${good.name}</b> <br><img src="http://shop-roles.node.ed.asmer.org.ua/${good.images[0].url}" style="width: 100px"/> <br> Цена: <b>$${good.price}</b><br><br>`
|
|
|
+
|
|
|
+ const calculation = document.createElement('div')
|
|
|
+
|
|
|
+ const buttonAdd = document.createElement('button')
|
|
|
+ buttonAdd.innerHTML = '+'
|
|
|
+ buttonAdd.setAttribute('type', 'button');
|
|
|
+ buttonAdd.onclick = async () => {
|
|
|
+ inputCount.value = +inputCount.value + 1
|
|
|
+ await store.dispatch(actionCartChange({ _id: _id, name: good.name, price: good.price, images: good.images }, +inputCount.value))
|
|
|
+
|
|
|
+ cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2><br>`
|
|
|
+ }
|
|
|
+ calculation.append(buttonAdd)
|
|
|
+
|
|
|
+ const inputCount = document.createElement('input')
|
|
|
+ inputCount.value = +count
|
|
|
+ inputCount.disabled = 'disabled'
|
|
|
+ inputCount.className = 'inputCount'
|
|
|
+ calculation.append(inputCount)
|
|
|
+
|
|
|
+ const buttonLess = document.createElement('button')
|
|
|
+ buttonLess.innerHTML = '-'
|
|
|
+ buttonLess.setAttribute('type', 'button');
|
|
|
+ buttonLess.onclick = async () => {
|
|
|
+ if ((+inputCount.value) > 1) {
|
|
|
+ inputCount.value = +inputCount.value - 1
|
|
|
+ await store.dispatch(actionCartChange({ _id: _id, name: good.name, price: good.price, images: good.images }, +inputCount.value))
|
|
|
+
|
|
|
+ cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2><br>`
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ calculation.append(buttonLess)
|
|
|
+
|
|
|
+ const buttonDelete = document.createElement('button')
|
|
|
+ buttonDelete.innerText = 'Delete'
|
|
|
+ buttonDelete.className = 'buttonDelete'
|
|
|
+ buttonDelete.setAttribute('type', 'button');
|
|
|
+ buttonDelete.onclick = async () => {
|
|
|
+ await store.dispatch(actionCartDelete({ _id: _id, name: good.name, price: good.price, images: good.images }))
|
|
|
+
|
|
|
+ card.style.display = 'none'
|
|
|
+ cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2><br>`
|
|
|
+ }
|
|
|
+ card.append(calculation)
|
|
|
+ card.append(buttonDelete)
|
|
|
+ main.append(card)
|
|
|
+ }
|
|
|
+ const cardTotalDiv = document.createElement('div')
|
|
|
+ cardTotalDiv.id = 'total'
|
|
|
+ const cardTotal = document.createElement('div')
|
|
|
+
|
|
|
+ cardTotal.innerHTML = `<br><h2>Total: <b style="color:green;">$${goodPrice()}</b></h2>`
|
|
|
+
|
|
|
+ cardTotalDiv.append(cardTotal)
|
|
|
+
|
|
|
+ let cartAlert = document.createElement('div')
|
|
|
+ cartAlert.innerHTML = `<h2 style='color:orange;'>Your cart seems empty 😟</h2>`
|
|
|
+ cartAlert.style.display = 'none'
|
|
|
+ cartAlert.id = 'cart-alert'
|
|
|
+
|
|
|
+ if (localStorage.authToken != '') {
|
|
|
+ const button = document.createElement('button')
|
|
|
+ button.innerHTML += '<strong>ORDER</strong>'
|
|
|
+ button.setAttribute('type', 'button');
|
|
|
+
|
|
|
+ if(goodPrice() != 0){
|
|
|
+ button.onclick = async () => {
|
|
|
+ await store.dispatch(actionAddOrder(Object.entries(store.getState().cart).map(([_id, count]) => ({ count: count.count, good: { _id } }))));
|
|
|
+ await store.dispatch(actionCartClear());
|
|
|
+ document.location.href = "#/orderFind";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else{
|
|
|
+ cartAlert.style.display = 'flex'
|
|
|
+ }
|
|
|
+
|
|
|
+ // button.className = 'btn btn-primary'
|
|
|
+ cardTotalDiv.append(button)
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ main.append(cardTotalDiv, cartAlert)
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (route in routes) {
|
|
|
+ routes[route]()
|
|
|
+ }
|
|
|
+}
|
|
|
+window.onhashchange()
|
|
|
+
|
|
|
+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
|
|
|
+}
|
|
|
+
|
|
|
+window.onhashchange()
|
|
|
+
|
|
|
+function goodPrice() {
|
|
|
+ return Object.entries(store.getState().cart).map(i => x += i[1].count * i[1].good.price, x = 0).reverse()[0] || 0
|
|
|
+}
|