Vladislav342 3 years ago
parent
commit
23e793aea9
1 changed files with 731 additions and 0 deletions
  1. 731 0
      HW_19/index.html

+ 731 - 0
HW_19/index.html

@@ -0,0 +1,731 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="utf-8">
+	<title>Korzinap!!!!!!!!!!!!!!!</title>
+	<style>
+    .Jorik{
+      border-bottom: 1px dotted green;
+    }
+		  #content {
+        display: flex;
+      }
+      #aside {
+        width: 30%;
+      }
+
+      #aside > a {
+        display: block;
+      }
+
+      .wrapper{
+      	border: 1px dotted green;
+      }
+
+      img{
+    	 width:300px;
+    		}
+  	   main{
+  			padding-left: 20px;
+  		}
+  		table{
+  			border:1px solid black;
+  		}
+  		td{
+  			text-align: center;
+  			border:1px solid black;
+  		}
+      
+      header {
+        display: flex;
+        justify-content: space-between;
+      }
+      #ownCab, .wrapper{
+        display: flex;
+        justify-content: space-between;
+      }
+      #ownCab{
+        margin-left: 1em;
+      }
+      #ownCab a, button{
+        margin-left: 1em;
+      }
+      .cart img{
+        width: 1.5em;
+        height: 1.5em;
+      }
+      a {
+        text-decoration: none;
+        color:blue;
+      }
+      a:hover{
+        color: green;
+      }
+	</style>
+</head>
+<body>
+
+	<header>
+      <div id="formId"></div>
+          <div class="wrapper"> 
+              <div class="cart">
+                  <a href="#/cart">
+                      <span id="cartQuantity"></span>
+                      <img src="xz.png" />
+                  </a>
+              </div>
+          <div id="ownCab"></div>
+      </div>     
+  </header>
+  <div id='content'>
+      <aside id='aside'>
+          Категории
+      </aside>
+      <main id='main'>
+          Контент
+      </main>
+  </div>
+
+	<script>
+//----------------------Store---------------------------------------
+		      function createStore(reducer){
+              let state       = reducer(undefined, {}) 
+              let cbs         = []                    
+              const getState  = () => state           
+              const subscribe = cb => (cbs.push(cb),   
+                                       () => cbs = cbs.filter(c => c !== cb))          
+              const dispatch  = action => { 
+                  if (typeof action === 'function'){ 
+                      return action(dispatch, getState) 
+                  }
+                  const newState = reducer(state, action) 
+                  if (newState !== state){
+                      state = newState 
+                      for (let cb of cbs)  cb()
+                  }
+              } 
+              return {
+                  getState, 
+                  dispatch,
+                  subscribe 
+              }
+          }
+
+//-----------------------------------------------------------------------------
+    //написать jwtDecode = token =>({})
+    const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MWE0ZTA1MmM3NTBjMTJiYTZiYTQwMjkiLCJsb2dpbiI6InZsYWRCcmF1bjQiLCJhY2wiOlsiNjFhNGUwNTJjNzUwYzEyYmE2YmE0MDI5IiwidXNlciJdfSwiaWF0IjoxNjM4NTM5NTUzfQ.oPRus9nGS1rg69eKu8rK-tMi4V-hN5HXE0NOzAc5K4k";
+
+    //выкусить из токена серединку 
+    //сделать base64 декод (atob)
+    //с результатом сделать JSON.parse
+    const jwtDecode = token =>{
+      try{
+        let mid=token.split('.');
+        let tok=mid[1];
+        let tokenDecode=atob(tok);
+        let finalTok=JSON.parse(tokenDecode);
+        return finalTok;
+      }catch(e){
+        console.log(e);
+      }
+    }
+    console.log(jwtDecode(token));
+
+//-------------------------------------------------------------------------------
+    function authReducer(state, {type, token}) {
+        if(!state) {
+            if (localStorage.authToken) {
+                type = 'AUTH_LOGIN'
+                token = localStorage.authToken
+            } else {
+                return {}        
+            } 
+        }
+        if (type === 'AUTH_LOGIN') {
+            localStorage.authToken = token
+            let payload = jwtDecode(token)
+            if (typeof payload !== 'object') {
+                return {}
+            } 
+            return {token, payload}
+        }
+        if (type === 'AUTH_LOGOUT') {
+            // debugger
+            localStorage.removeItem('authToken')
+            return {}
+        }
+        return state
+    }
+
+// const store = createStore(authReducer)
+const actionAuthLogin = token => ({type: 'AUTH_LOGIN', token})
+const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
+
+// login.onclick = () => store.dispatch(actionAuthLogin(token))
+// logout.onclick = () => store.dispatch(actionAuthLogout())
+
+//------------------------------------------------------------------------------
+    function combineReducers(reducers) {
+      return (state = {}, action) => {
+          const newState = {}
+          for (const [reducerName, reducer] of Object.entries(reducers)) {
+              let newSubstate = reducer(state[reducerName], action)
+              if (newSubstate !== state[reducerName]) {
+                  newState[reducerName] = newSubstate
+              }
+          }
+          if (Object.entries(newState).length !== 0) {
+              return {...state, ...newState}
+          } else return state
+      } 
+    }
+
+//-------------------------------------------------------------------------------
+function promiseReducer(state = {}, { type, name, status, payload, error }) {
+  //{
+  //    login: {status, payload, error}
+  //    catById: {status, payload, error}
+  //}
+  if (type === 'PROMISE') {
+    return {
+      ...state,
+      [name]: { status, payload, error }
+    }
+  }
+  return state
+}
+
+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 store = createStore(promiseReducer)
+// store.subscribe(() => console.log(store.getState()))
+
+// const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
+// store.dispatch(actionPromise('delay1000', delay(1000)))
+// store.dispatch(actionPromise('delay2000', delay(2000)))
+// store.dispatch(actionPromise('failedfetch', fetch('https://swapi.dev/api/people/1/')
+// .then(res => res.json())))
+
+//--------------------------------------------------------------
+      function cartReducer(state = {}, {type, good = {}, count = 1}) {
+        const {_id} = good
+        
+        if(!count) {
+            return state
+        }
+      const types = {
+            CART_ADD(){ 
+                //берет старую позицию, и добавляет count к текущему количеству.
+                //если позиции нет - то добавляет к 0 (т. е. в первый раз будет count)
+                count = +count
+                return {
+                    ...state,
+                   [_id]: {good, count:(state[_id] ? state[_id].count : 0) + count}
+                }
+            },
+            CART_CHANGE(){ 
+                //тупо меняет позицию
+                return {
+                    ...state,
+                    [_id]: {good, count}
+                }
+            },
+            CART_REMOVE(){ 
+                //надо как-то создать объект без ключа
+                let {[_id]: remove, ...rest} = state
+                  return rest
+            },
+            CART_CLEAR(){ 
+                //самое простое
+                return {}
+            }
+        }
+
+        if(type in types) {
+            return types[type]()
+        }
+        return state
+      }
+
+      const actionCartAdd = (good, count) => ({type: 'CART_ADD', good, count})
+      const actionCartChange = (good, count) => ({type: 'CART_CHANGE', good, count})
+      const actionCartRemove = (good) => ({type: 'CART_REMOVE', good})
+      const actionCartClear = () => ({type: 'CART_CLEAR'})
+//-------------------------------------------------------------------------
+const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}) //тут еще
+const store = createStore(combinedReducer)
+console.log(store.getState())
+
+// store.dispatch(actionPromise('delay1000', delay('1000')))
+// store.dispatch(actionAuthLogin(token))
+
+//--------------------------------------------------------------------------------
+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.errors && !data.data)
+          throw new Error(JSON.stringify(data.errors))
+        return data.data[Object.keys(data.data)[0]]
+      })
+
+    const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
+
+    const gql = getGQL(backURL + '/graphql')
+
+//-------------------------Actions----------------------------------------
+    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 description images {
+            url
+          }
+        }
+        subCategories {
+          name _id goods {
+            _id name description
+          }
+        }
+      }
+    }`, { q: JSON.stringify([{ _id }]) }))
+
+    const actionGoodById = (_id) =>
+      actionPromise('goodById', gql(`query goodById($q: String){
+            GoodFindOne(query: $q){
+                _id name description price images{
+                    url
+                }
+            }
+        }`, { q: JSON.stringify([{ _id }]) }))
+
+    store.dispatch(actionRootCats())
+
+    const actionOrder = () =>
+    async (dispatch, getState) => {
+        let {cart} = store.getState()
+        const orderGoods = Object.entries(cart)
+          .map(([_id, {good, count}]) => ({good: {_id}, count}))
+        let result = await dispatch(actionPromise('order', gql(`
+              mutation newOrder($order:OrderInput){
+                OrderUpsert(order:$order)
+                  { _id total }
+                }`, {order: {orderGoods}})))  
+        if(result?._id) {
+              store.dispatch(actionCartClear())
+        }
+    }
+
+    const actionMyOrders = () =>
+      actionPromise('orderfind', gql(`query orderfind{
+        OrderFind(query: "[{}]"){
+          _id  createdAt total orderGoods{
+            _id  createdAt price count
+            good{
+              _id description name images{
+                _id url
+              }
+            }
+          }
+        }
+      }`))
+
+      const actionLogin = (login, password) => 
+        actionPromise('login', gql(`query login($login: String, $password: String){
+          login(login: $login, password: $password)
+        }`, {login: login, password: password})
+      ) 
+
+      const actionFullLogin = (login, password) => 
+        async dispatch => {
+            let token = await dispatch(actionLogin(login, password))
+            if(token) {
+                dispatch(actionAuthLogin(token))
+            }
+        }
+
+      const actionRegister = (login, password) => 
+        actionPromise('register', gql(`mutation registration($login: String, $password:String) {
+          UserUpsert(user: {login: $login, 
+                    password:$password, 
+                    nick: $login}){
+            _id login
+          }
+        }`, {login: login, password: password})
+      )
+
+      const actionFullRegister = (login, password) => 
+        async dispatch => {
+            let log = await dispatch(actionRegister(login, password))
+            if (log) {
+              let token = await dispatch(actionLogin(login, password))
+              if (token){
+                  dispatch(actionAuthLogin(token))
+              }
+          }
+      }
+//--------------------------main Page--------------------------------------------
+    store.subscribe(() => {
+      const { promise } = store.getState()
+      console.log('------------')
+      console.log(promise)
+      if (promise?.rootCats?.payload) {
+        aside.innerHTML = ''
+        for (const { _id, name } of promise?.rootCats?.payload) {
+          const link = document.createElement('a')
+          link.href = `#/category/${_id}`
+          link.innerText = name
+          aside.append(link)
+        }
+      }
+    })
+
+    store.subscribe(() => {
+      const { promise } = store.getState()
+      const [, route, _id] = location.hash.split('/')
+      if (promise?.catById?.payload && route === 'category') {
+        const { name } = promise.catById.payload
+        main.innerHTML = `<h1 class="Jorik">${name}</h1>`
+        if (promise.catById.payload?.subCategories) {
+            for (let { _id, name } of promise.catById.payload.subCategories) {
+              const podCat     = document.createElement('a')
+              podCat.href      = `#/category/${_id}`
+              podCat.innerText = name
+              main.append(podCat)
+            }
+        }
+        for (const good of promise.catById.payload.goods) {
+            const {_id, name, price, images} = good
+            const card        = document.createElement('div')
+            card.innerHTML    = `<h2>${name}</h2>
+                    <img src="${backURL}/${images[0].url}"/>
+                        <div>
+                            <b>Стоимость:</b> <b><sub>${price}UAH</sub></b>
+                            <br><a href=#/good/${_id}>Страница товара</a>
+                        </div>`                  
+            main.append(card)
+            let btn        = document.createElement('button')
+            btn.innerText  = 'Добавить в корзину'
+            btn.style.marginLeft=50+'%'
+            btn.onclick     = () => store.dispatch(actionCartAdd(good, 1))
+            card.append(btn)
+        }
+      }
+    })
+
+//-----------------------Korzina---------------------------
+    store.subscribe(() => {
+      const {cart} 				 = store.getState()
+      console.log(cart)
+      if (cart){
+        let num                  = 0
+        cartQuantity.innerText   = ''
+        for (let key in cart) {
+          	num+=cart[key].count
+        }
+        cartQuantity.innerText   = num
+      }
+    })
+
+    function showCart() {
+      const { cart } = store.getState()
+      console.log(cart)
+      main.innerHTML       = ``
+      let num              = 1
+      let count            = 0
+      let total            = 0
+      let h                = document.createElement('h2')
+      h.innerText          = 'Корзина';
+      h.style.borderBottom = 1+'px solid black' 
+      main.append(h)
+      const table = document.createElement('table')
+      for (let key in cart) {
+        let { good, count } = cart[key]
+        console.log(good, count)
+        let cartKey = document.createElement('tr')
+        cartKey.innerHTML = `<td>${num++}</td><td><img src="${backURL}/${good.images[0].url}"/></td><td>${good.name}</td>
+        	<td>${good.price} UAH</td>`
+        let addBtnTd        = document.createElement('td')
+        let addBtn          = document.createElement('button')
+        addBtn.innerText    = '+'
+        addBtnTd.append(addBtn)
+        let chngTd          = document.createElement('td')
+        let chng            = document.createElement('input')
+        chng.type           = 'number'
+        chng.min            = 0
+        chng.max            = 50
+        chng.value          = count
+        chngTd.append(chng)
+        let subBtnTd        = document.createElement('td')
+        let subBtn          = document.createElement('button')
+        subBtn.innerText    = '-'
+        subBtnTd.append(subBtn)
+        let deleteBtnTd     = document.createElement('td')
+        let deleteBtn       = document.createElement('button')
+        deleteBtn.innerText = 'Удалить'
+        deleteBtnTd.append(deleteBtn)
+        chng.oninput        = () => store.dispatch(actionCartChange(good, chng.value))
+        deleteBtn.onclick   = () => {
+            cartKey.remove()
+            store.dispatch(actionCartRemove(good))   
+        }
+        addBtn.onclick      = () => {
+          store.dispatch(actionCartAdd(good, 1))
+          chng.value++
+        }
+        subBtn.onclick      = () => {
+          store.dispatch(actionCartAdd(good, -1))
+          chng.value--
+        }
+        cartKey.append(addBtnTd, chngTd, subBtnTd, deleteBtnTd)
+        table.append(cartKey)
+        main.append(table)
+        count              += parseInt(chng.value)
+        total              += good.price * chng.value
+      }
+      let clearBtn          = document.createElement('button')
+      clearBtn.innerText    = "Очистить корзину";
+      Object.entries(cart).length > 0 ? main.append(clearBtn) : null 
+      clearBtn.onclick      = () => {
+          store.dispatch(actionCartClear())
+          table.remove()
+          clearBtn.style.display = 'none';
+          canOrder.style.display = 'none';
+      }
+      let canOrder          = document.createElement('button')
+      canOrder.innerText    = "Оформить заказ"
+      Object.entries(cart).length > 0 ? main.append(canOrder) : null
+      if (localStorage.authToken) {
+        canOrder.disabled = false
+      } else {
+        canOrder.disabled = true
+      }
+      canOrder.onclick       = () => {
+          store.dispatch(actionOrder())     
+          table.remove()
+          clearBtn.style.display = 'none';
+          canOrder.style.display = 'none';
+      }
+    }
+
+//-----------------opis tovara--------------------------
+    store.subscribe(() => {
+      const { promise } = store.getState()
+      const [, route, _id] = location.hash.split('/')
+      if (promise?.goodById?.payload && route === 'good' && location.href.includes(`#/good/${_id}`)) {
+        main.innerHTML = ``
+        let { _id, name, price, images, description } = promise.goodById.payload
+          let goodItem = document.createElement('div')
+          goodItem.className = 'good_item'
+          goodItem.innerHTML = `<h2>${name}</h2>
+              <img src="${backURL}/${images[0].url}"/>
+              <div>
+                  <p><b>Стоимость:</b> <b><sub>${price} UAH</sub></b></strong></p>
+                  <p><b>Описание:</b> <sub>${description}</sub></p>
+              </div>`
+          main.append(goodItem)
+      }
+    })
+
+//--------------------------Orders-----------------------------
+    store.subscribe(() => {
+      const {promise} = store.getState()
+      const [,route, _id] = location.hash.split('/')
+      if (promise?.orderfind?.payload && route === 'dashboard'){
+          main.innerHTML = ''
+          let title = document.createElement('h2')
+          title.innerText = 'Ваши заказы';
+          title.style.textAlign='left'
+          title.style.borderBottom=1+'px solid black'
+          main.append(title)
+          for (let order of promise.orderfind.payload) {
+            let quantity = 0
+            const {total, orderGoods} = order
+            let table = document.createElement('table')
+            let thead = document.createElement('thead')
+            thead.innerHTML = `<th class="user-order">Дата заказа</th>
+            		<th>Наименование</th>
+            		<th>Количество</th>
+            		<th>Цена</th>`
+            table.append(thead)
+            for (let {createdAt, count, good, price} of orderGoods){
+                quantity += count;
+                let date = new Date(+createdAt).toLocaleDateString();
+                let tr = document.createElement('tr')
+                tr.innerHTML = `<td>${date}</td>
+                		<td><figure class='good-img'>
+                			<img src="${backURL}/${good.images[0].url}">
+                			<figcaption>${good.name}</figcaption>
+                		</figure></td>
+                		<td>${count}</td>
+                		<td>${price}</td>`
+                table.append(tr)
+            }
+            let totalOrderAmount = document.createElement('tr')
+            totalOrderAmount.innerHTML = `<th class="user-order">Всего товаров в заказе: ${quantity}</th>
+            	<th>Общая сумма заказа: ${total}</th>`
+            table.append(totalOrderAmount)
+            main.append(table)
+        }
+      }
+      else if (!promise?.orderfind?.payload && route === 'dashboard'){
+          main.innerHTML = ''
+          let title = document.createElement('h2')
+          title.textContent = 'Вы еще не сделали заказ';
+          main.append(title)
+      }
+    })
+
+//----------------------Login-------------------------------
+    store.subscribe(() => {
+      const {auth} = store.getState()
+      const {payload} = auth
+      if (payload?.sub ) {
+          ownCab.innerHTML = ''
+          //ownCab.style.marginTop=10+'px'
+          //ownCab.style.border=5+'px solid blue'
+          //ownCab.style.width=17+'%'
+          const {id, login}  = payload.sub
+          const userName = document.createElement('div')
+          userName.innerHTML = `Hello, ${login}`
+          const userOrders = document.createElement('a')
+          //userOrders.style.marginRight=10+'px'
+          userOrders.innerText = 'Your Orders'
+          userOrders.href = `#/dashboard/`
+          let logout = document.createElement('button')
+          logout.textContent = 'Exit'
+          logout.onclick = () => {
+              formId.innerHTML = ''
+              store.dispatch(actionAuthLogout());
+          }
+          ownCab.append(userName, userOrders, logout)
+      } else {
+        ownCab.innerHTML = ''
+      }
+    })
+
+    store.subscribe(() => {
+      const {auth} = store.getState()
+      if (!auth?.payload){
+          formId.innerHTML = '';
+          let twoBtns=document.createElement('span')
+          let loginBtn = document.createElement('a');
+          let regBtn = document.createElement('a');
+          twoBtns.append(loginBtn, regBtn)
+          twoBtns.style.border=1+'px dotted green'
+          formId.append(twoBtns)
+          loginBtn.innerText = 'LogIn'
+          regBtn.innerText = 'Registration'
+          loginBtn.onclick = () => {
+              loginBtn.style.display = 'none'
+              regBtn.style.display= 'none'
+              let form = document.createElement('form') 
+              let login = document.createElement('input')
+              login.type = 'text'
+              login.placeholder = 'Login'
+              let password = document.createElement('input')
+              password.placeholder = 'Password'
+              password.type = 'password'
+              let btn = document.createElement('a')
+              btn.innerText = 'Enter'
+              btn.onclick = () => {
+                  if (login.value !== '' && password.value !== '') {
+                      btn.href = `#/login/${login.value}*${password.value}`
+                  }
+                  formId.innerHTML = ''
+              }
+              form.append(login, password, btn)
+              formId.append(form)         
+          }
+          regBtn.onclick = () => {
+              loginBtn.style.display = 'none'
+              regBtn.style.display= 'none'
+              let form = document.createElement('form')
+              let login = document.createElement('input')
+              login.type = 'text'
+              login.placeholder = 'Login'
+              let pass = document.createElement('input')
+              pass.placeholder = 'Password'
+              pass.type = 'password'
+              let btn = document.createElement('a')
+              btn.innerText = 'Registration'
+              btn.onclick = () => {
+                  if (login.value !== '' && pass.value !== '') {
+                      btn.href = `#/register/${login.value}*${pass.value}`
+                  }
+                  formId.innerHTML = ''
+              }
+              form.append(login, password, button)
+              formId.append(form)
+          }
+      }
+    })
+
+//-------------------------------------------------------------
+    window.onhashchange = () => {
+      const [, route, _id] = location.hash.split('/')
+
+      const routes = {
+        category() {
+          store.dispatch(actionCatById(_id))
+        },
+        good() {
+          //задиспатчить actionGoodById
+          store.dispatch(actionGoodById(_id)) 
+          console.log('ТОВАРОСТРАНИЦА')
+        },
+        login(){
+          let data = _id.split('*')
+          store.dispatch(actionFullLogin(data[0], data[1]))
+          console.log('ЛОГИН')
+        },
+        register(){
+          let data = _id.split('*')
+          store.dispatch(actionFullRegister(data[0], data[1]))
+          console.log('РЕГА')
+        },
+        cart(){
+          showCart()
+          console.log('Сделать страницу с позициями, полями ввода колличества, картинками и кнопкой')
+        }, 
+        dashboard(){
+          store.dispatch(actionMyOrders())
+          console.log('Прочитать бывшие заказы')
+        }
+      }
+      if (route in routes)
+        routes[route]()
+    }
+    window.onhashchange()
+	</script>
+</body>
+</html>