index.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>authReducer</title>
  6. <style>
  7. #mainContainer{
  8. display:flex;
  9. }
  10. #aside{
  11. width: 30%;
  12. }
  13. #aside > a{
  14. display: block;
  15. }
  16. img{
  17. width:300px;
  18. }
  19. main{
  20. padding-left: 20px;
  21. }
  22. table{
  23. border:1px solid black;
  24. }
  25. td{
  26. text-align: center;
  27. border:1px solid black;
  28. }
  29. </style>
  30. </head>
  31. <body>
  32. <header>
  33. <div id="formId"></div>
  34. <div id="ownCab"></div>
  35. </header>
  36. <div id='mainContainer'>
  37. <aside id='aside'>
  38. Категории
  39. </aside>
  40. <main id='main'>
  41. Контент
  42. </main>
  43. </div>
  44. <input type="submit" value="click 1" id="btn1">
  45. <input type="submit" value="click 2" id="btn2">
  46. <script>
  47. function createStore(reducer){
  48. let state = reducer(undefined, {})
  49. let cbs = []
  50. const getState = () => state
  51. const subscribe = cb => (cbs.push(cb),
  52. () => cbs = cbs.filter(c => c !== cb))
  53. const dispatch = action => {
  54. if (typeof action === 'function'){
  55. return action(dispatch, getState)
  56. }
  57. const newState = reducer(state, action)
  58. if (newState !== state){
  59. state = newState
  60. for (let cb of cbs) cb()
  61. }
  62. }
  63. return {
  64. getState,
  65. dispatch,
  66. subscribe
  67. }
  68. }
  69. //----------------------------------------------------------------
  70. //написать jwtDecode = token =>({})
  71. const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MWE0ZTA1MmM3NTBjMTJiYTZiYTQwMjkiLCJsb2dpbiI6InZsYWRCcmF1bjQiLCJhY2wiOlsiNjFhNGUwNTJjNzUwYzEyYmE2YmE0MDI5IiwidXNlciJdfSwiaWF0IjoxNjM4NTM5NTUzfQ.oPRus9nGS1rg69eKu8rK-tMi4V-hN5HXE0NOzAc5K4k";
  72. //выкусить из токена серединку
  73. //сделать base64 декод (atob)
  74. //с результатом сделать JSON.parse
  75. const jwtDecode = token =>{
  76. try{
  77. let mid=token.split('.');
  78. let tok=mid[1];
  79. let tokenDecode=atob(tok);
  80. let finalTok=JSON.parse(tokenDecode);
  81. return finalTok;
  82. }catch(e){
  83. console.log(e);
  84. }
  85. }
  86. console.log(jwtDecode(token));
  87. //------------------------------------------------------------
  88. function authReducer(state={},{type, token}){
  89. if(!state){
  90. if(localStorage.authToken){
  91. type="AUTH_LOGIN";
  92. token=localStorage.authToken;
  93. }else{
  94. return {};
  95. }
  96. }
  97. if(type==='AUTH_LOGIN'){
  98. //сохранить в localStorage token
  99. //вернуть {token, payload: jwtDecode}
  100. //если payload не обьект, то вернуть {}
  101. //вернуть {token, payload}
  102. localStorage.authToken=token;
  103. let payload=jwtDecode(token);
  104. if(typeof payload==='object'){
  105. return {
  106. token,
  107. payload
  108. }
  109. }else{
  110. return {};
  111. }
  112. }
  113. if(type==='AUTH_LOGOUT'){
  114. //удалить из localStorage token и вернуть {}
  115. delete localStorage.authToken;
  116. return {};
  117. }
  118. return state
  119. }
  120. const actionAuthLogin = token => ({type:'AUTH_LOGIN', token})
  121. const actionAuthLogout = () => ({type:'AUTH_LOGOUT'})
  122. //---------------------------------------------------------------
  123. function combineReducers(reducers){
  124. return (state={},action)=>{
  125. let newState={};
  126. for(const [reducerName, reducer] of Object.entries(reducers)){
  127. let newSubState=reducer(state[reducerName],action);
  128. if(newSubState!==state[reducerName]){
  129. newState[reducerName]=newSubState;
  130. }
  131. }
  132. if(0 !==Object.keys(newState).length){
  133. return{
  134. ...state,
  135. ...newState
  136. }
  137. }else{
  138. return state;
  139. }
  140. //перебрать все редьюсеры
  141. //запустить каждый из них
  142. //передать при этом в него его Ветвь общего state и экшен как есть
  143. //получить newSubState
  144. //если newSubState отлиается от входящего, то записать newSubState в newState
  145. //после цикла, если newState не пуст, то вернуть {...state, ...newState}
  146. //иначе вернуть old state
  147. }
  148. }
  149. //-------------------------------------------------------------------------------
  150. function promiseReducer(state={}, {type, status, payload, error, name}){
  151. if (type === 'PROMISE'){
  152. return {
  153. ...state,
  154. [name]:{status, payload, error}
  155. }
  156. }
  157. return state;
  158. }
  159. const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
  160. const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
  161. const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
  162. const actionPromise = (name, promise) =>
  163. async dispatch => {
  164. dispatch(actionPending(name))
  165. try {
  166. let payload = await promise
  167. dispatch(actionResolved(name, payload))
  168. return payload
  169. }
  170. catch (error) {
  171. dispatch(actionRejected(name, error))
  172. }
  173. }
  174. //------------------------------------------------------------------------------------------
  175. const combinedReducer= combineReducers({promise:promiseReducer,auth:authReducer});
  176. const store=createStore(combinedReducer)
  177. console.log(store.getState());
  178. //const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  179. //store.dispatch(actionPromise('delay1000',delay(1000)))//{promise:{delay1000:''},auth:{}}
  180. //store.dispatch(actionAuthLogin(token))//{promise:{delay1000:''},auth:{token...}}
  181. /*let store=createStore(authReducer);
  182. store.subscribe(() => console.log(store.getState()));*/
  183. btn1.onclick = () => store.dispatch(actionAuthLogin(token));
  184. btn2.onclick = () => store.dispatch(actionAuthLogout());
  185. /*const actionLogin = (login,password) =>
  186. actionPromise('catById', gql('ЗАПРОС НА ЛОГИН', {login, password}))
  187. const actionFullLogin=(login, password) =>
  188. async dispatch=>{
  189. let token=await dispatch(actionLogin(login, password))
  190. if(token){
  191. dispatch(actionLogin(actionAuthLogin(token)))
  192. }
  193. }*/
  194. //----------------------------------------------------------------------------------
  195. // const actionRegister // actionPromise
  196. // const actionFullLogin = (login, pasword) => //actionRegister + actionFullLogin
  197. // + интерфейс к этому - форму логина, регистрации, может повесить это на #/login #/register
  198. // + #/orders показывает Ваши бывшие заказы
  199. // сделать actionMyOrders
  200. //-----------------------------------------------------------------------------------
  201. const getGQL = url =>
  202. (query, variables = {}) =>
  203. fetch(url, {
  204. method: 'POST',
  205. headers: {
  206. "Content-Type": "application/json",
  207. ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
  208. },
  209. body: JSON.stringify({ query, variables })
  210. })
  211. .then(res => res.json())
  212. .then(data => {
  213. if (data.errors && !data.data)
  214. throw new Error(JSON.stringify(data.errors))
  215. return data.data[Object.keys(data.data)[0]]
  216. })
  217. const backURL='http://shop-roles.asmer.fs.a-level.com.ua'
  218. const gql=getGQL(backURL+'/graphql')
  219. //------------------------------------------Actions------------------------------------------------
  220. const actionLogin = (login, password) =>
  221. actionPromise('login', gql(`query login($login: String, $password: String){
  222. login(login: $login, password: $password)
  223. }`, {login: login, password: password})
  224. )
  225. const actionFullLogin = (login, password) =>
  226. async dispatch => {
  227. let token = await dispatch(actionLogin(login, password))
  228. if(token) {
  229. dispatch(actionAuthLogin(token))
  230. }
  231. }
  232. const actionRegister = (login, password) =>
  233. actionPromise('register', gql(`mutation registration($login: String, $password:String) {
  234. UserUpsert(user: {login: $login,
  235. password:$password,
  236. nick: $login}){
  237. _id login
  238. }
  239. }`, {login: login, password: password})
  240. )
  241. const actionFullRegister = (login, password) =>
  242. async dispatch => {
  243. let log = await dispatch(actionRegister(login, password))
  244. if (log) {
  245. let token = await dispatch(actionLogin(login, password))
  246. if (token){
  247. dispatch(actionAuthLogin(token))
  248. }
  249. }
  250. }
  251. const actionRootCats = () =>
  252. actionPromise('rootCats', gql(`query {
  253. CategoryFind(query: "[{\\"parent\\":null}]"){
  254. _id name
  255. }
  256. }`))
  257. const actionCatById = (_id) => //добавить подкатегории
  258. actionPromise('catById', gql(`query catById($q: String){
  259. CategoryFindOne(query: $q) {
  260. _id name goods{
  261. _id name price description images {
  262. url
  263. }
  264. }
  265. subCategories {
  266. name _id goods {
  267. _id name description
  268. }
  269. }
  270. }
  271. }`, { q: JSON.stringify([{ _id }]) }))
  272. const actionGoodById = (_id) =>
  273. actionPromise('goodById', gql(`query goodById($q: String){
  274. GoodFindOne(query: $q){
  275. _id name description price images{
  276. url
  277. }
  278. }
  279. }`, { q: JSON.stringify([{ _id }]) }))
  280. store.dispatch(actionRootCats())
  281. //--------------------front-------------------------------------------------
  282. store.subscribe(() => {
  283. const {promise} = store.getState()
  284. console.log('------------')
  285. console.log(promise)
  286. if (promise?.rootCats?.payload) {
  287. aside.innerHTML = ''
  288. for (const { _id, name } of promise?.rootCats?.payload) {
  289. const link = document.createElement('a')
  290. link.href = `#/category/${_id}`
  291. link.innerText = name
  292. aside.append(link)
  293. }
  294. }
  295. })
  296. store.subscribe(() => {
  297. const { promise } = store.getState()
  298. const [, route, _id] = location.hash.split('/')
  299. if (promise?.catById?.payload && route === 'category') {
  300. const { name } = promise.catById.payload
  301. main.innerHTML = `<h1>${name}</h1>`
  302. if (promise.catById.payload?.subCategories) {
  303. for (let {_id, name} of promise.catById.payload.subCategories) {
  304. const podCat = document.createElement('a')
  305. podCat.className = 'sub_cat'
  306. podCat.href = `#/category/${_id}`
  307. podCat.textContent = name
  308. main.append(podCat)
  309. }
  310. }
  311. for (const { _id, name, price, images } of promise.catById.payload.goods) {
  312. const card = document.createElement('div')
  313. card.innerHTML = `<h2>${name}</h2>
  314. <img src="${backURL}/${images[0].url}"/>
  315. <div>
  316. <b>Стоимость:</b> <b><sub>${price}UAH</sub></b>
  317. <br><a href=#/good/${_id}>Страница товара</a>
  318. </div>`
  319. main.append(card)
  320. }
  321. }
  322. })
  323. //-------------------opis tovara-------------------------------
  324. store.subscribe(() => {
  325. const {promise} = store.getState()
  326. const [, route, _id] = location.hash.split('/')
  327. if (promise?.goodById?.payload && route === 'good' && location.href.includes(`#/good/${_id}`)) {
  328. main.innerHTML = ``
  329. let {_id, name, price, images, description} = promise.goodById.payload
  330. let item = document.createElement('div')
  331. item.className = 'good_item'
  332. item.innerHTML =`<h2>${name}</h2>
  333. <img src="${backURL}/${images[0].url}"/>
  334. <div>
  335. <p><b>Стоимость:</b> <b><sub>${price} UAH</sub></b></strong></p>
  336. <p><b>Описание:</b> <sub>${description}</sub></p>
  337. </div>`
  338. main.append(item)
  339. }
  340. })
  341. //-----------------Orders------------------------------------------------
  342. const ActionMyOrders = () =>
  343. actionPromise('orderfind', gql(`query orderfind{
  344. OrderFindOne(query:"[{}]"){
  345. _id createdAt total orderGoods{
  346. _id createdAt price count good{
  347. _id name description images{
  348. _id url
  349. }
  350. }
  351. }
  352. }
  353. }`))
  354. store.subscribe(() => {
  355. const {promise} = store.getState()
  356. const [,route, _id] = location.hash.split('/')
  357. if (promise?.orderfind?.payload && route === 'orders'){
  358. let counT = 0
  359. main.innerHTML = ''
  360. const {total, orderGoods} = promise.orderfind.payload
  361. let title = document.createElement('h2')
  362. title.textContent = 'Ваши заказы';
  363. title.className = 'title'
  364. let table = document.createElement('table')
  365. let tr = document.createElement('thead')
  366. tr.innerHTML = `<th>Дата заказа</th><th>Название</th><th>Количество</th><th>Цена</th>`
  367. table.append(tr)
  368. for (let {createdAt, count, good, price} of orderGoods){
  369. counT += count;
  370. let date = new Date(+createdAt).toLocaleDateString();
  371. let tr = document.createElement('tr')
  372. tr.innerHTML = `<td>${date}</td>
  373. <td><figure><img src="${backURL}/${good.images[0].url}"><figcaption>${good.name}</figcaption></figure></td>
  374. <td>${count}</td><td>${price}</td>`
  375. table.append(tr)
  376. }
  377. let tr2 = document.createElement('tr')
  378. tr2.innerHTML = `<th>Всего товаров в заказе: ${counT}</th><th>Общая сумма заказа: ${total}</th>`
  379. table.append(tr2)
  380. main.append(title, table)
  381. }else if(!promise?.orderfind?.payload && route === 'orders'){
  382. main.innerHTML = ''
  383. let title = document.createElement('h2')
  384. title.textContent = 'Вы еще не сделали заказ';
  385. main.append(title)
  386. }
  387. })
  388. //-------------------Login-------------------------------------------------
  389. store.subscribe(() => {
  390. const {auth} = store.getState()
  391. const {payload} = auth
  392. if (payload?.sub ) {
  393. ownCab.innerHTML = ''
  394. ownCab.style.marginTop=10+'px'
  395. ownCab.style.border=5+'px solid blue'
  396. ownCab.style.width=17+'%'
  397. const {id, login} = payload.sub
  398. const userName = document.createElement('div')
  399. userName.innerHTML = `Hello, ${login}`
  400. const userOrders = document.createElement('a')
  401. userOrders.innerText = 'Your Orders'
  402. userOrders.style.marginRight=10+'px'
  403. userOrders.href = `#/orders/`
  404. let logout = document.createElement('button')
  405. logout.textContent = 'Exit'
  406. logout.onclick = () => {
  407. formId.innerHTML = ''
  408. store.dispatch(actionAuthLogout());
  409. }
  410. ownCab.append(userName, userOrders, logout)
  411. }else{
  412. ownCab.innerHTML = ''
  413. }
  414. })
  415. store.subscribe(() => {
  416. const {auth} = store.getState()
  417. if (!auth?.payload){
  418. formId.innerHTML = '';
  419. let loginBtn = document.createElement('a');
  420. loginBtn.style.border=1+'px solid green'
  421. let regBtn = document.createElement('a');
  422. formId.append(loginBtn, regBtn)
  423. loginBtn.textContent = 'LogIn'
  424. regBtn.style.marginLeft=10+'px'
  425. regBtn.style.border=1+'px solid yellow'
  426. regBtn.textContent = 'Registration'
  427. loginBtn.onclick = () => {
  428. loginBtn.style.display = 'none'
  429. regBtn.style.display= 'none'
  430. let form = document.createElement('form')
  431. let login = document.createElement('input')
  432. login.type = 'text'
  433. login.placeholder = 'Login'
  434. let password = document.createElement('input')
  435. password.placeholder = 'Password'
  436. password.type = 'password'
  437. let button = document.createElement('a')
  438. button.textContent = 'Enter'
  439. button.onclick = () => {
  440. if (login.value !== '' && password.value !== '') {
  441. button.href = `#/login/${login.value}*${password.value}`
  442. }
  443. }
  444. form.append(login, password, button)
  445. formId.append(form)
  446. }
  447. regBtn.onclick = () => {
  448. loginBtn.style.display = 'none'
  449. regBtn.style.display= 'none'
  450. let form = document.createElement('form')
  451. let login = document.createElement('input')
  452. login.type = 'text'
  453. login.placeholder = 'Login'
  454. let password = document.createElement('input')
  455. password.placeholder = 'Password'
  456. password.type = 'password'
  457. let button = document.createElement('a')
  458. button.textContent = 'Registration'
  459. button.onclick = () => {
  460. if (login.value !== '' && password.value !== '') {
  461. button.href = `#/register/${login.value}*${password.value}`
  462. }
  463. }
  464. form.append(login, password, button)
  465. formId.append(form)
  466. }
  467. }
  468. })
  469. //-----------------------------------------------------------------------
  470. window.onhashchange = () => {
  471. const [, route, _id] = location.hash.split('/')
  472. const routes = {
  473. category() {
  474. store.dispatch(actionCatById(_id))
  475. },
  476. good() {
  477. store.dispatch(actionGoodById(_id))
  478. //задиспатчить actionGoodById
  479. console.log('ТОВАРОСТРАНИЦА')
  480. },
  481. login(){
  482. let data = _id.split('*')
  483. store.dispatch(actionFullLogin(data[0], data[1]))
  484. },
  485. register(){
  486. let data = _id.split('*')
  487. store.dispatch(actionFullRegister(data[0], data[1]))
  488. },
  489. orders(){
  490. store.dispatch(ActionMyOrders())
  491. }
  492. }
  493. if (route in routes)
  494. routes[route]()
  495. }
  496. window.onhashchange()
  497. </script>
  498. </body>
  499. </html>