script.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. //********REDUX*********
  2. function createStore(reducer){
  3. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  4. let cbs = [] //массив подписчиков
  5. const getState = () => state //функция, возвращающая переменную из замыкания
  6. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  7. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  8. const dispatch = action => {
  9. if (typeof action === 'function'){ //если action - не объект, а функция
  10. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  11. }
  12. const newState = reducer(state, action) //пробуем запустить редьюсер
  13. if (newState !== state){ //проверяем, смог ли редьюсер обработать action
  14. state = newState //если смог, то обновляем state
  15. for (let cb of cbs) cb() //и запускаем подписчиков
  16. }
  17. }
  18. return {
  19. getState, //добавление функции getState в результирующий объект
  20. dispatch,
  21. subscribe //добавление subscribe в объект
  22. }
  23. }
  24. const jwtDecode = token => {
  25. console.log(token)
  26. try {
  27. return JSON.parse(atob(token.split('.')[1]))
  28. }
  29. catch (e){
  30. console.log("ERROR" , e.message)
  31. }
  32. }
  33. function authReducer(state, {type, token}) {
  34. if (!state){
  35. if (localStorage.authToken) {
  36. type = 'AUTH_LOGIN'
  37. token = localStorage.authToken
  38. }
  39. else {
  40. return {}
  41. }
  42. }
  43. if (type === 'AUTH_LOGIN'){
  44. let payload = jwtDecode(token)
  45. if(payload){
  46. localStorage.authToken = token
  47. return {
  48. ...state,
  49. token,
  50. payload
  51. }
  52. }
  53. return {}
  54. }
  55. if (type === 'AUTH_LOGOUT'){
  56. delete localStorage.authToken
  57. return {}
  58. }
  59. return state
  60. }
  61. const actionAuthLogin = token => ({type: 'AUTH_LOGIN', token})
  62. const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
  63. function promiseReducer(state={}, {type, status, payload, error, name}){
  64. if (type === 'PROMISE'){
  65. return {
  66. ...state,
  67. [name]:{status, payload, error}
  68. }
  69. }
  70. return state;
  71. }
  72. const actionPending = name => ({type: 'PROMISE', status: 'PENDING', name})
  73. const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
  74. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  75. const actionPromise = (name, promise) =>
  76. async dispatch => {
  77. dispatch(actionPending(name))
  78. try {
  79. let data = await promise
  80. dispatch(actionResolved(name, data))
  81. return data
  82. }
  83. catch(error){
  84. dispatch(actionRejected(name, error))
  85. }
  86. }
  87. function combineReducers(reducers) {
  88. return (state={}, action) => {
  89. const newState = {}
  90. for (const [reducerName, reducer] of Object.entries(reducers)) {
  91. const newSubState = reducer(state[reducerName], action)
  92. if(newSubState !== state[reducerName]) {
  93. newState[reducerName] = newSubState
  94. }
  95. }
  96. if (Object.entries(newState).length !== 0){
  97. return {...state, ...newState}
  98. }
  99. else{
  100. return state
  101. }
  102. }
  103. }
  104. const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer})
  105. const store = createStore(combinedReducer)
  106. store.subscribe(() => console.log(store.getState()))
  107. //const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms));
  108. //store.dispatch(actionPromise('delay1000', delay(1000)))
  109. //store.dispatch(actionAuthLogin(token))
  110. //*****************************************************************************
  111. const getGQL = url =>
  112. async (query, variables={}) => {
  113. let obj = await fetch(url, {
  114. method: 'POST',
  115. headers: localStorage.authToken ? {
  116. "Content-Type": "application/json",
  117. "Authorization": "Bearer " + localStorage.authToken
  118. } : {"Content-Type": "application/json"},
  119. body: JSON.stringify({ query, variables })
  120. })
  121. let a = await obj.json()
  122. if (!a.data && a.errors)
  123. throw new Error(JSON.stringify(a.errors))
  124. return a.data[Object.keys(a.data)[0]]
  125. }
  126. const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
  127. const gql = getGQL(backURL + '/graphql');
  128. // Отрисовка страницы с категориями и товарами
  129. const actionRootCats = () =>
  130. actionPromise('rootCats', gql(`query {
  131. CategoryFind(query: "[{\\"parent\\":null}]"){
  132. _id name
  133. }
  134. }`))
  135. const actionCatById = (_id) => //добавить подкатегории
  136. actionPromise('catById', gql(`query catById($q: String){
  137. CategoryFindOne(query: $q){
  138. _id name,
  139. goods{
  140. _id name price images {
  141. url
  142. }
  143. },
  144. subCategories{
  145. _id name, subCategories{
  146. name
  147. }
  148. }
  149. }
  150. }`, {q: JSON.stringify([{_id}])}))
  151. const actionGoodById = (_id) =>
  152. actionPromise('goodById', gql(`query goodById($q: String){
  153. GoodFindOne(query: $q){
  154. _id name description price images{
  155. url
  156. }
  157. }
  158. }`, {q: JSON.stringify([{_id}])}))
  159. // отрисовка заказов
  160. const actionOrderFindOne = () =>
  161. actionPromise('orderfind', gql(`query orderfind{
  162. OrderFindOne(query:"[{}]"){
  163. _id createdAt total orderGoods{
  164. _id createdAt price count good{
  165. _id name description images{
  166. _id url
  167. }
  168. }
  169. }
  170. }
  171. }`))
  172. store.dispatch(actionRootCats())
  173. store.subscribe(() => {
  174. const {promise} = store.getState()
  175. if (promise?.rootCats?.payload){
  176. aside.innerHTML = ''
  177. for (const {_id, name} of promise?.rootCats?.payload){
  178. const link = document.createElement('a')
  179. link.href = `#/category/${_id}`
  180. link.innerText = name
  181. aside.append(link)
  182. }
  183. // отрисовка заказов
  184. get.innerHTML = ''
  185. let btnGet = document.createElement('a');
  186. btnGet.textContent = 'order find'
  187. btnGet.href = '#/orders/'
  188. get.append(btnGet)
  189. }
  190. })
  191. store.subscribe(() => {
  192. const {promise} = store.getState()
  193. const [,route, _id] = location.hash.split('/')
  194. if (promise?.catById?.payload && route === 'category'){
  195. const {name} = promise.catById.payload
  196. main.innerHTML = `<h1>${name}</h1>`
  197. if(promise.catById.payload?.subCategories){
  198. for (const {_id , name} of promise.catById.payload.subCategories) {
  199. const link = document.createElement('a')
  200. link.href = `#/category/${_id}`
  201. link.textContent = name
  202. main.append(link)
  203. }
  204. }
  205. for (const {_id, name, price, images} of promise.catById.payload.goods){
  206. const card = document.createElement('div')
  207. card.className = 'item--main'
  208. card.innerHTML = `<h2>${name}</h2>
  209. <img src="${backURL}/${images[0].url}" />
  210. <strong>Цена: ${price} USD</strong><br>
  211. <a href="#/good/${_id}">Ссылка на страницу товара: #/good/${_id}</a>`
  212. main.append(card)
  213. }
  214. }
  215. })
  216. store.subscribe(() => {
  217. const {promise} = store.getState()
  218. const [,route, _id] = location.hash.split('/')
  219. if (promise?.goodById?.payload && route === 'good' && window.location.href.includes(`#/good/${_id}`)){
  220. const {_id ,name, images, price, description } = promise.goodById.payload
  221. main.innerHTML = ''
  222. let div = document.createElement('div')
  223. div.className = 'item--main'
  224. div.innerHTML = `<h1>${name}</h1>
  225. <img src="${backURL}/${images[0].url}" />
  226. <p><strong>Описание:</strong> <br> ${description}</p>
  227. <p><strong>ID:</strong> ${_id}</p>
  228. <strong>Цена: ${price} USD</strong>`
  229. main.append(div);
  230. }
  231. })
  232. //*****************************************************************************
  233. //Отрисовка страницы с формой авторизации
  234. //const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MWE0ZGFjZGM3NTBjMTJiYTZiYTQwMTUiLCJsb2dpbiI6ImFsZXhyZXpuaWNoZW5rbyIsImFjbCI6WyI2MWE0ZGFjZGM3NTBjMTJiYTZiYTQwMTUiLCJ1c2VyIl19LCJpYXQiOjE2MzgxOTQxMDN9.HCRoXBSLYcswzfYk5kPmtN9cx-7XYBRZzi-WDt_kNQk';
  235. //const login = "alexreznichenko"
  236. //const password = "alexreznichenko"
  237. const actionLogin = (login, password) =>
  238. actionPromise('login', gql(`query login($login: String, $password: String){
  239. login(login: $login, password: $password)
  240. }`, {login: login, password: password}))
  241. const actionFullLogin = (login, password) =>
  242. async dispatch => {
  243. let token = await dispatch(actionLogin(login, password))
  244. if (token){
  245. dispatch(actionAuthLogin(token))
  246. }
  247. }
  248. const actionRegister = (login, password) =>
  249. actionPromise('register', gql(`mutation register($login:String, $password: String){
  250. UserUpsert(user:{
  251. login: $login,
  252. password: $password,
  253. nick: $login}){
  254. _id login
  255. }
  256. }`, {login: login, password: password}))
  257. const actionFullRegister = (login, password) =>
  258. async dispatch => {
  259. let allow = await dispatch(actionRegister(login, password))
  260. if (allow) {
  261. let token = await dispatch(actionLogin(login, password))
  262. if (token){
  263. console.log('okay')
  264. dispatch(actionAuthLogin(token))
  265. }
  266. }
  267. }
  268. // создание кнопок авторизации
  269. store.subscribe(() => {
  270. const {auth} = store.getState()
  271. if (!auth?.payload){
  272. wrapper.innerHTML = '';
  273. let signIn = document.createElement('a');
  274. let signUp = document.createElement('a');
  275. signIn.textContent = 'Sign in'
  276. signUp.textContent = 'Sign up'
  277. signIn.onclick = () => {
  278. signIn.style.display = 'none'
  279. signUp.style.display = 'none'
  280. let form = document.createElement('from')
  281. let logInp = document.createElement('input')
  282. let passInp = document.createElement('input')
  283. let btnReg = document.createElement('a')
  284. logInp.placeholder = 'login'
  285. logInp.type = 'text'
  286. passInp.placeholder = 'password'
  287. passInp.type = 'password'
  288. btnReg.textContent = 'Log In'
  289. btnReg.onclick = () => {
  290. if (logInp.value !== '' && passInp.value !== '') {
  291. btnReg.href = `#/login/${logInp.value}*${passInp.value}`
  292. }
  293. }
  294. form.append(logInp, passInp, btnReg)
  295. wrapper.append(form)
  296. }
  297. signUp.onclick = () => {
  298. signIn.style.display = 'none'
  299. signUp.style.display = 'none'
  300. let form = document.createElement('from')
  301. let logInp = document.createElement('input')
  302. let passInp = document.createElement('input')
  303. let btnReg = document.createElement('a')
  304. logInp.placeholder = 'login'
  305. logInp.type = 'text'
  306. passInp.placeholder = 'password'
  307. passInp.type = 'password'
  308. btnReg.textContent = 'Registration'
  309. btnReg.onclick = () => {
  310. if (logInp.value !== '' && passInp.value !== '') {
  311. btnReg.href = `#/register/${logInp.value}*${passInp.value}`
  312. }
  313. }
  314. form.append(logInp, passInp, btnReg)
  315. wrapper.append(form)
  316. }
  317. wrapper.append(signIn, signUp)
  318. }
  319. })
  320. // отрисовка пользователя
  321. store.subscribe(() => {
  322. const {auth} = store.getState()
  323. if (auth?.payload){
  324. wrapper.innerHTML = ''
  325. let {iat} = auth.payload
  326. let {id, login} = auth.payload.sub
  327. let date = new Date(iat * 1000);
  328. let hours = date.getHours();
  329. let minutes = "0" + date.getMinutes();
  330. let seconds = "0" + date.getSeconds();
  331. let formattedTime = hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
  332. let allow = document.createElement('div');
  333. allow.style.display = 'flex'
  334. allow.style.flexDirection = 'column'
  335. allow.innerHTML = `<strong>Вы успешно авторизированны</strong>
  336. <strong>Ваш логин: ${login}</strong>
  337. <strong>Ваш ID: ${id}</strong>
  338. <strong>Время авторизации: ${formattedTime}</strong>`
  339. let btnLogout = document.createElement('button')
  340. btnLogout.textContent = 'LOGOUT'
  341. btnLogout.onclick = () => {
  342. wrapper.innerHTML = ''
  343. store.dispatch(actionAuthLogout());
  344. }
  345. wrapper.append(allow, btnLogout)
  346. }
  347. })
  348. // отрисовка всех заказов
  349. store.subscribe(() => {
  350. const {promise} = store.getState()
  351. const [,route, _id] = location.hash.split('/')
  352. if (promise?.orderfind?.payload && route === 'orders'){
  353. let counts = 0
  354. let amound = 0
  355. main.innerHTML = ''
  356. const {createdAt, total, orderGoods} = promise.orderfind.payload
  357. let h = document.createElement('h1')
  358. h.textContent = 'Your orders';
  359. let table = document.createElement('table')
  360. let tr = document.createElement('thead')
  361. tr.innerHTML = `<th>Номер заказа</th><th>Время добавления заказа</th><th>Количество</th><th>Товар</th><th>Цена</th>`
  362. table.append(tr)
  363. for (let {createdAt, count, good, price} of orderGoods){
  364. amound += count;
  365. let date = new Date(+createdAt);
  366. let hours = date.getHours();
  367. let minutes = "0" + date.getMinutes();
  368. let seconds = "0" + date.getSeconds();
  369. let formattedTime = hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
  370. let tr = document.createElement('tr')
  371. let td = document.createElement('td')
  372. tr.innerHTML = `<td>${counts++}</td><td>${formattedTime}</td><td>${count}</td>
  373. <td class="table-order-td"><span>${good.name}</span><img src="${backURL}/${good.images[0].url}"></td><td>${price}</td>`
  374. table.append(tr)
  375. }
  376. let tr2 = document.createElement('tr')
  377. let date = new Date(+createdAt);
  378. let hours = date.getHours();
  379. let minutes = "0" + date.getMinutes();
  380. let seconds = "0" + date.getSeconds();
  381. let formattedTime = hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
  382. tr2.innerHTML = `<th></th><th>${formattedTime}</th><th>${amound}</th><th></th><th>${total}</th>`
  383. table.append(tr2)
  384. main.append(h, table)
  385. }
  386. else if (!promise?.orderfind?.payload && route === 'orders'){
  387. main.innerHTML = ''
  388. let h = document.createElement('h1')
  389. h.textContent = 'У вас ещё нет заказов';
  390. main.append(h)
  391. }
  392. })
  393. window.onhashchange = () => {
  394. const [,route, _id] = location.hash.split('/')
  395. const routes = {
  396. category(){
  397. store.dispatch(actionCatById(_id))
  398. },
  399. good(){
  400. store.dispatch(actionGoodById(_id))
  401. },
  402. login(){
  403. let data = _id.split('*')
  404. store.dispatch(actionFullLogin(data[0], data[1]))
  405. },
  406. register(){
  407. let data = _id.split('*')
  408. store.dispatch(actionFullRegister(data[0], data[1]))
  409. },
  410. orders(){
  411. store.dispatch(actionOrderFindOne())
  412. }
  413. }
  414. if (route in routes)
  415. routes[route]()
  416. }
  417. window.onhashchange()