<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>authReducer</title> <style> #mainContainer{ display:flex; } #aside{ width: 30%; } #aside > a{ display: block; } img{ width:300px; } main{ padding-left: 20px; } table{ border:1px solid black; } td{ text-align: center; border:1px solid black; } </style> </head> <body> <header> <div id="formId"></div> <div id="ownCab"></div> </header> <div id='mainContainer'> <aside id='aside'> Категории </aside> <main id='main'> Контент </main> </div> <input type="submit" value="click 1" id="btn1"> <input type="submit" value="click 2" id="btn2"> <script> 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 token //вернуть {token, payload: jwtDecode} //если payload не обьект, то вернуть {} //вернуть {token, payload} localStorage.authToken=token; let payload=jwtDecode(token); if(typeof payload==='object'){ return { token, payload } }else{ return {}; } } if(type==='AUTH_LOGOUT'){ //удалить из localStorage token и вернуть {} delete localStorage.authToken; return {}; } return state } const actionAuthLogin = token => ({type:'AUTH_LOGIN', token}) const actionAuthLogout = () => ({type:'AUTH_LOGOUT'}) //--------------------------------------------------------------- function combineReducers(reducers){ return (state={},action)=>{ let newState={}; for(const [reducerName, reducer] of Object.entries(reducers)){ let newSubState=reducer(state[reducerName],action); if(newSubState!==state[reducerName]){ newState[reducerName]=newSubState; } } if(0 !==Object.keys(newState).length){ return{ ...state, ...newState } }else{ return state; } //перебрать все редьюсеры //запустить каждый из них //передать при этом в него его Ветвь общего state и экшен как есть //получить newSubState //если newSubState отлиается от входящего, то записать newSubState в newState //после цикла, если newState не пуст, то вернуть {...state, ...newState} //иначе вернуть old state } } //------------------------------------------------------------------------------- function promiseReducer(state={}, {type, status, payload, error, name}){ 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)) try { let payload = await promise dispatch(actionResolved(name, payload)) return payload } catch (error) { dispatch(actionRejected(name, error)) } } //------------------------------------------------------------------------------------------ const combinedReducer= combineReducers({promise:promiseReducer,auth:authReducer}); const store=createStore(combinedReducer) console.log(store.getState()); //const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms)) //store.dispatch(actionPromise('delay1000',delay(1000)))//{promise:{delay1000:''},auth:{}} //store.dispatch(actionAuthLogin(token))//{promise:{delay1000:''},auth:{token...}} /*let store=createStore(authReducer); store.subscribe(() => console.log(store.getState()));*/ btn1.onclick = () => store.dispatch(actionAuthLogin(token)); btn2.onclick = () => store.dispatch(actionAuthLogout()); /*const actionLogin = (login,password) => actionPromise('catById', gql('ЗАПРОС НА ЛОГИН', {login, password})) const actionFullLogin=(login, password) => async dispatch=>{ let token=await dispatch(actionLogin(login, password)) if(token){ dispatch(actionLogin(actionAuthLogin(token))) } }*/ //---------------------------------------------------------------------------------- // const actionRegister // actionPromise // const actionFullLogin = (login, pasword) => //actionRegister + actionFullLogin // + интерфейс к этому - форму логина, регистрации, может повесить это на #/login #/register // + #/orders показывает Ваши бывшие заказы // сделать actionMyOrders //----------------------------------------------------------------------------------- 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 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)) } } } 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()) //--------------------front------------------------------------------------- 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>${name}</h1>` if (promise.catById.payload?.subCategories) { for (let {_id, name} of promise.catById.payload.subCategories) { const podCat = document.createElement('a') podCat.className = 'sub_cat' podCat.href = `#/category/${_id}` podCat.textContent = name main.append(podCat) } } for (const { _id, name, price, images } of promise.catById.payload.goods) { 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) } } }) //-------------------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 item = document.createElement('div') item.className = 'good_item' item.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(item) } }) //-----------------Orders------------------------------------------------ const ActionMyOrders = () => actionPromise('orderfind', gql(`query orderfind{ OrderFindOne(query:"[{}]"){ _id createdAt total orderGoods{ _id createdAt price count good{ _id name description images{ _id url } } } } }`)) store.subscribe(() => { const {promise} = store.getState() const [,route, _id] = location.hash.split('/') if (promise?.orderfind?.payload && route === 'orders'){ let counT = 0 main.innerHTML = '' const {total, orderGoods} = promise.orderfind.payload let title = document.createElement('h2') title.textContent = 'Ваши заказы'; title.className = 'title' let table = document.createElement('table') let tr = document.createElement('thead') tr.innerHTML = `<th>Дата заказа</th><th>Название</th><th>Количество</th><th>Цена</th>` table.append(tr) for (let {createdAt, count, good, price} of orderGoods){ counT += count; let date = new Date(+createdAt).toLocaleDateString(); let tr = document.createElement('tr') tr.innerHTML = `<td>${date}</td> <td><figure><img src="${backURL}/${good.images[0].url}"><figcaption>${good.name}</figcaption></figure></td> <td>${count}</td><td>${price}</td>` table.append(tr) } let tr2 = document.createElement('tr') tr2.innerHTML = `<th>Всего товаров в заказе: ${counT}</th><th>Общая сумма заказа: ${total}</th>` table.append(tr2) main.append(title, table) }else if(!promise?.orderfind?.payload && route === 'orders'){ 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.innerText = 'Your Orders' userOrders.style.marginRight=10+'px' userOrders.href = `#/orders/` 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 loginBtn = document.createElement('a'); loginBtn.style.border=1+'px solid green' let regBtn = document.createElement('a'); formId.append(loginBtn, regBtn) loginBtn.textContent = 'LogIn' regBtn.style.marginLeft=10+'px' regBtn.style.border=1+'px solid yellow' regBtn.textContent = '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 button = document.createElement('a') button.textContent = 'Enter' button.onclick = () => { if (login.value !== '' && password.value !== '') { button.href = `#/login/${login.value}*${password.value}` } } form.append(login, password, button) 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 password = document.createElement('input') password.placeholder = 'Password' password.type = 'password' let button = document.createElement('a') button.textContent = 'Registration' button.onclick = () => { if (login.value !== '' && password.value !== '') { button.href = `#/register/${login.value}*${password.value}` } } form.append(login, password, button) formId.append(form) } } }) //----------------------------------------------------------------------- window.onhashchange = () => { const [, route, _id] = location.hash.split('/') const routes = { category() { store.dispatch(actionCatById(_id)) }, good() { store.dispatch(actionGoodById(_id)) //задиспатчить actionGoodById console.log('ТОВАРОСТРАНИЦА') }, login(){ let data = _id.split('*') store.dispatch(actionFullLogin(data[0], data[1])) }, register(){ let data = _id.split('*') store.dispatch(actionFullRegister(data[0], data[1])) }, orders(){ store.dispatch(ActionMyOrders()) } } if (route in routes) routes[route]() } window.onhashchange() </script> </body> </html>