Vika 2 years ago
parent
commit
4ae77a4a21
3 changed files with 467 additions and 0 deletions
  1. 40 0
      js17/index.html
  2. 423 0
      js17/main.js
  3. 4 0
      js17/style.css

+ 40 - 0
js17/index.html

@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <title>GQL</title>
+        <meta charset='utf8' />
+        <style>
+            #mainContainer {
+                display: flex;
+            }
+            #aside {
+                width: 30%;
+            }
+            #aside > a{
+                display: block;
+            }
+        </style>
+        <link rel="stylesheet" href="style.css">
+    </head>
+    <body>
+        <header>КУДА Я ПОПАЛ?</header>
+        <div id='mainContainer'>
+            <aside id='aside'>
+                Категории
+            </aside>
+            <main id='main'>
+                <div id="wrapperLogin">
+                    <a href="#/login">
+                        <button>Login</button>
+                    </a>
+                    <a href="#/register"> 
+                        <button>Registration</button>
+                    </a>
+                </div>
+                <div id="wrapperSubCategory"></div>
+                Контент
+            </main>
+        </div>
+        <script src='main.js'></script>
+    </body>
+</html>

+ 423 - 0
js17/main.js

@@ -0,0 +1,423 @@
+//debugger;
+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}){
+  // {
+  //    login: {status, payload, error}
+  //    catById: {status, payload, error}
+  // }
+  if (type === 'PROMISE'){
+      return {
+          ...state,
+          [name]:{status, payload, error}
+      }
+  }
+  return state
+}
+
+//const store = createStore(promiseReducer);
+
+function jwtDecode(token){
+  try {let base64Url = token.split('.')[1];
+  let base64 = atob(base64Url);
+  return JSON.parse(base64); 
+  } catch { (err) =>
+    console.log(err);
+  }
+                                        //раскодировать токен:
+                                          //выкусить середочку
+                                          //atob
+                                          //JSON.parse
+                                          //на любом этапе могут быть исключения
+}
+
+function authReducer(state, {type, token}){
+  if (!state){
+      if(localStorage.authToken){  //проверить localStorage.authToken на наличие
+        return {
+        'type': 'AUTH_LOGIN',
+        'token':localStorage.authToken,
+        };                         //если есть - сделать так, что бы следующий if сработал
+      }  else { return state = {}}                            //если нет - вернуть {}
+  }
+  if (type === 'AUTH_LOGIN'){
+    const bigToken =  jwtDecode(token);   //взять токен из action
+    if (bigToken) {                                //попытаться его jwtDecode
+      localStorage.setItem('authToken', token);                              //если удалось, то:
+      return { 
+        token,
+        payload: bigToken,
+      }
+    }                                              //сохранить токен в localStorage
+  }                                                //вернуть объект вида {токен, payload: раскодированный токен}
+  if (type === 'AUTH_LOGOUT'){
+    localStorage.clear();                          //почистить localStorage
+    return {};                                     //вернуть пустой объект
+  }
+  return state
+}
+
+const actionAuthLogin  = token => ({type: 'AUTH_LOGIN', token})
+const actionAuthLogout = ()    => ({type: 'AUTH_LOGOUT'})
+
+//const store = createStore(authReducer)
+
+                                                                         //стартовое состояние может быть с токеном
+
+function combineReducers(reducers){                                      //перебрать все редьюсеры
+  return (state={}, action) => {                                         //запустить каждый их них
+    const newState = {}                                                  //передать при этом в него ЕГО ВЕТВЬ общего state, и action как есть   
+    for (const [reducerName, reducer] of Object.entries(reducers)){      //получить newSubState
+      let newSubState = reducer(state[reducerName], action);             //если newSubState отличается от входящего, то записать newSubState в newState
+      if (newSubState !== state[reducerName]) {                          //после цикла, если newState не пуст, то вернуть {...state, ...newState} 
+        newState[reducerName] = newSubState;                             //иначе вернуть state
+      }
+    }                                                                     //{promise: {}, auth: {}}
+
+    if (Object.keys(newState).length !== 0) {
+      return {
+        ... state,
+        ... newState,
+      }
+    }
+    return state;
+  }
+}
+const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer});
+const store = createStore(combinedReducer);
+
+
+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)) 
+      try{
+          let payload = await promise
+          dispatch(actionResolved(name, payload))
+          return payload;
+      }
+      catch(error){
+          dispatch(actionRejected(name, error))
+      }
+  }
+
+  const getGQL = url =>
+  (query, variables = {}) =>
+      fetch(url, {
+      //метод
+      method: 'POST',
+      headers: {
+          //заголовок content-type
+          "Content-Type": "application/json",
+          ...(localStorage.authToken ? {"Authorization": "Bearer " + localStorage.authToken} :
+                                       {})
+      },
+      //body с ключами query и variables 
+      body: JSON.stringify({query, variables})
+      })
+      .then(res => res.json())
+      .then(data => {
+          if (data.errors && !data.data)
+              throw new Error(JSON.stringify(data.errors))
+          return data.data[Object.keys(data.data)[0]]
+      })
+
+const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
+  
+const gql = getGQL(`${backURL}/graphql`)
+
+
+                                          //store.dispatch(actionPromise('delay1000', delay(1000)))//{promise: {delay1000: '''}, auth: {}}
+                                          //store.dispatch(actionAuthLogin(token))//{promise: {delay1000: '''}, auth: {token .....}}
+                                          //
+                                          //+ ПЕРЕДЕЛАТЬ ОТОБРАЖЕНИЕ с поправкой на то, что теперь промисы не в корне state а в state.promise
+
+const actionLogin = (login, password) =>
+    actionPromise('login', gql(`query authorize ($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) =>                  //const actionRegister //actionPromise
+  actionPromise('register', gql(`mutation register($user:UserInput){
+    UserUpsert(user:$user){
+      _id login
+    }
+  }`, {"user":{
+        "login": `${login}`,
+        "password": `${password}`
+        }
+      }
+    )
+  )
+               
+const actionFullRegister = (login, password) =>   //const actionFullRegister = (login, password) => //actionRegister + actionFullLogin
+  async dispatch => {      
+    dispatch(actionAuthLogout());                      //+ интерфейс к этому - форму логина, регистрации, может повесить это на #/login #/register 
+    let reg = await dispatch(actionRegister(login, password)); 
+    if(!reg){
+      dispatch(actionFullLogin(login, password))
+    }                                                    //+ #/orders показывает ваши бывшие заказы:
+  }                                                    //сделать actionMyOrders
+                                               
+
+  
+    //проверить:
+    //поделать store.dispatch с разными action. Скопипастить токен
+    //проверить перезагрузку страницы.
+    
+//const store = createStore(promiseReducer)
+   
+
+store.subscribe(() => console.log(store.getState()))
+
+
+
+
+const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
+
+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){
+         name subCategories {
+          _id
+          name 
+          goods {
+           _id name description price images{
+              url
+            }
+         }
+       }goods {
+         _id name price images {
+           url
+         }
+       }
+     }
+   }`, {"q": JSON.stringify([{_id}])}))
+
+// store.dispatch(actionRootCats())
+
+const actionGoodById = (_id) =>
+   actionPromise('goodById', gql(`query goodById($goodId:String){
+    GoodFindOne(query:$goodId){
+        _id name description price images{
+        _id text url
+      }
+    }
+  }`, {"goodId": JSON.stringify([{_id}])}))
+  
+
+store.subscribe(() => {
+  const {rootCats} = store.getState().promise
+  if (rootCats?.payload){
+      aside.innerHTML = ''
+      for (const {_id, name} of rootCats?.payload){
+          const link      = document.createElement('a')
+          link.href       = `#/category/${_id}`
+          link.innerText  = name
+          aside.append(link)
+      }
+  }
+})
+
+window.onhashchange = () => {
+  const [, route, _id] = location.hash.split('/'); 
+
+  const routes = {
+      category(){
+          store.dispatch(actionCatById(_id))
+      },
+      good(){ 
+          store.dispatch(actionGoodById(_id));//задиспатчить actionGoodById
+          console.log('ТОВАРОСТРАНИЦА')
+
+      },
+      login(){
+        document.getElementById('main').innerHTML = "";     //отрисовка тут
+        const formRegistration = document.createElement('form');           //по кнопке - store.dispatch(actionFullLogin(login, password))
+        const inputLogin = document.createElement('input');
+        inputLogin.placeholder = "Login";
+        inputLogin.style.display="block";
+
+        const inputPassword = document.createElement('input');
+        inputPassword.placeholder = "Password";
+        inputPassword.style.display="block";
+        inputPassword.style.marginTop = "10px";
+
+        const buttonSignIn = document.createElement('button');
+        buttonSignIn.innerText = "Sign in";
+        buttonSignIn.style.marginTop = "10px";
+
+        const buttonReset = document.createElement('button');
+        buttonReset.type ="reset";
+        buttonReset.innerText =" Reset";
+        buttonReset.style.marginTop = "10px";
+        buttonReset.style.marginLeft = "10px";
+
+        main.append(formRegistration);
+        formRegistration.append(inputLogin);
+        formRegistration.append(inputPassword);
+        formRegistration.append(buttonSignIn);
+        formRegistration.append(buttonReset);
+
+        buttonSignIn.addEventListener('click', async function enterStore () {
+          console.log('clicked')
+         await store.dispatch(actionFullLogin(inputLogin.value, inputPassword.value)); 
+          const result = store.getState().promise.login.payload;
+            if(result){
+              document.getElementById('main').innerHTML = ""; 
+              store.dispatch(actionRootCats());
+
+            }
+        })
+      },
+
+      register(){ 
+        document.getElementById('main').innerHTML = "";     //отрисовка тут
+        const formRegistration = document.createElement('form');           //по кнопке - store.dispatch(actionFullLogin(login, password))
+        const inputLogin = document.createElement('input');
+        inputLogin.placeholder = "Login";
+        inputLogin.style.display="block";
+
+        const inputPassword = document.createElement('input');
+        inputPassword.placeholder = "Password";
+        inputPassword.style.display="block";
+        inputPassword.style.marginTop = "10px";
+
+        const buttonSignIn = document.createElement('button');
+        buttonSignIn.innerText = "Sign up";
+        buttonSignIn.style.marginTop = "10px";
+
+        const buttonReset = document.createElement('button');
+        buttonReset.type ="reset";
+        buttonReset.innerText =" Reset";
+        buttonReset.style.marginTop = "10px";
+        buttonReset.style.marginLeft = "10px";
+
+        main.append(formRegistration);
+        formRegistration.append(inputLogin);
+        formRegistration.append(inputPassword);
+        formRegistration.append(buttonSignIn);
+        formRegistration.append(buttonReset);
+
+        buttonSignIn.addEventListener('click', async function enterStore () {
+          await store.dispatch(actionFullRegister(inputLogin.value, inputPassword.value)); 
+          console.log('store.getState().promise', store.getState().promise)
+          const result = store.getState().promise.register.payload; console.log("result",result)
+            if(result){
+              document.getElementById('main').innerHTML = ""; 
+              store.dispatch(actionRootCats());
+            }
+        })
+      },
+  }
+
+  if (route in routes)
+      routes[route]()
+  
+}
+window.onhashchange()
+
+store.subscribe(() => {
+  const {catById} = store.getState().promise
+  const [,route, _id] = location.hash.split('/')
+  if (catById?.payload && route === 'category'){
+      const {name, subCategories} = catById.payload;
+      let str = '';
+      if (subCategories){
+        for(let subCateg of subCategories){
+            str += `<h4><a href="#/subCategory/${subCateg._id}"> ${subCateg.name}</a></h4>`; console.log("subCateg", subCateg);
+            const {goods} = subCateg;
+            if (goods){
+              for(let good of goods) {
+                const {name, price, images, _id} = good;
+                str += `<h2>${name}</h2>
+                        <img src="${backURL}/${images[0].url}" />
+                        <strong>${price}</strong>
+                        <a href="#/good/${_id}">${name}</a>`
+              }
+            }
+        }
+      } else {str += 'Подкатегории отсутствуют'}
+
+      main.innerHTML = `<h1>${name}</h1> ${str} ` //ТУТ ДОЛЖНЫ БЫТЬ ПОДКАТЕГОРИИ
+      for (const {_id, name, price, images} of catById.payload.goods){
+          const card      = document.createElement('div')
+          card.innerHTML = `<h2>${name}</h2>
+                            <img src="${backURL}/${images[0].url}" />
+                            <strong>${price}</strong>
+                            <a href="#/good/${_id}">${name}</a> `
+           main.append(card)
+      }
+  }
+})                               
+                    
+store.subscribe(() => { 
+    const {goodById} = store.getState().promise;  //console.log("dfsdfs", store.getState().goodById)
+    if(goodById) {
+        const [, , _id] = location.hash.split('/');   //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
+        if(goodById?.payload && location.hash == `#/good/${_id}`){               //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
+            document.getElementById('main').innerHTML = "";  
+            const {name, description, price, images} = goodById.payload;  
+            const card = document.createElement('div');  //в таком случае очищаем main и рисуем информацию про товар с подробностями
+            card.innerHTML = `<h2>${name}</h2>
+                                <img src="${backURL}/${images[0].url}" />
+                                <strong>${price} $</strong>
+                                <p>${description}</p>`
+                                         
+        main.append(card);                
+    }
+    }
+})       
+
+
+
+
+

+ 4 - 0
js17/style.css

@@ -0,0 +1,4 @@
+#wrapperLogin {
+  position: absolute;
+  right: 0;
+}