index.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Magaz</title>
  8. <style>
  9. * *,
  10. *::before,
  11. *::after {
  12. box-sizing: border-box;
  13. margin: 0;
  14. padding: 0;
  15. }
  16. #page-wrapper {
  17. display: flex;
  18. flex-direction: column;
  19. min-height: 100vh;
  20. margin: 0 auto;
  21. max-width: 1170px;
  22. width: 100%;
  23. }
  24. #mainContainer {
  25. display: flex;
  26. flex-grow: 1;
  27. margin: 20px 0;
  28. }
  29. #header {
  30. height: 50px;
  31. background-color: #FF5319;
  32. border: 1px solid gray;
  33. padding: 10px 5px;
  34. font-size: 26px;
  35. display: flex;
  36. justify-content: space-between;
  37. }
  38. #userAuth li {
  39. list-style-type: none;
  40. display: inline-block;
  41. font-size: 18px;
  42. }
  43. a {
  44. text-decoration: none;
  45. }
  46. #aside {
  47. min-width: 25%;
  48. font-size: 20px;
  49. padding: 0 20px 0px 0;
  50. }
  51. #aside>a {
  52. display: block;
  53. color: black;
  54. }
  55. #aside>a:hover {
  56. color: tomato;
  57. font-size: 24px;
  58. }
  59. #main {
  60. display: flex;
  61. flex-wrap: wrap;
  62. width: 100%;
  63. }
  64. #footer {
  65. text-align: center;
  66. background-color: #FF5319;
  67. border: 1px solid gray;
  68. height: 40px;
  69. }
  70. #cartImg {
  71. height: 30px;
  72. width: 30px;
  73. }
  74. #nav {
  75. display: flex;
  76. }
  77. </style>
  78. </head>
  79. <body>
  80. <div id="page-wrapper">
  81. <header id="header">
  82. <div id="logo">Larek</div>
  83. <nav id="nav">
  84. <ul id="userAuth">
  85. <li>войти</li>
  86. <li>регистрация</li>
  87. </ul>
  88. <div id="cart">
  89. <a id="cartLink"><img id="cartImg"
  90. src="https://i.pinimg.com/originals/15/4f/df/154fdf2f2759676a96e9aed653082276.png"
  91. alt=""></a>
  92. </div>
  93. </nav>
  94. </header>
  95. <div id='mainContainer'>
  96. <aside id='aside'>
  97. Категории
  98. </aside>
  99. <main id='main'>
  100. Контент
  101. </main>
  102. </div>
  103. <footer id="footer">КУДА Я ПОПАЛ?2</footer>
  104. </div>
  105. <script>
  106. // debugger;
  107. function createStore(reducer) {
  108. let state = reducer(undefined, {})
  109. let cbs = []
  110. function dispatch(action) {
  111. if (typeof action === 'function') {
  112. return action(dispatch)
  113. }
  114. const newState = reducer(state, action)
  115. if (state !== newState) {
  116. state = newState
  117. cbs.forEach(cb => cb())
  118. }
  119. }
  120. return {
  121. dispatch,
  122. subscribe(cb) {
  123. cbs.push(cb)
  124. return () => cbs = cbs.filter(c => c !== cb)
  125. },
  126. getState() {
  127. return state
  128. }
  129. }
  130. }
  131. // function reducer(state = {}, { type, status, payload, error, name }) {
  132. // if (type === 'PROMISE') {
  133. // return {
  134. // ...state,
  135. // [name]: { status, payload, error }
  136. // }
  137. // }
  138. // return state
  139. // }
  140. /////
  141. function promiseReducer(state = {}, { type, status, payload, error, name }) {
  142. if (type === 'PROMISE') {
  143. return {
  144. ...state,
  145. [name]: { status, payload, error }
  146. }
  147. }
  148. return state
  149. }
  150. function cardReducer(state = {}, { type, count = 1, good }) {
  151. //придумать типы CART_ADD, CART_CLEAR, CART_SET, CART_DELETE
  152. //{ _id1: {count: 2, good: {....}},
  153. // _id2: 10,}
  154. //
  155. if (type === 'CART_ADD') { ///где то тут проеб
  156. console.log("+1")
  157. const _id = good._id
  158. return {
  159. ...state,
  160. [_id]: { count: (state[_id]?.count || 0) + count, good }
  161. }
  162. }
  163. if (type === 'CART_DELETE') {
  164. console.log("-1")
  165. const _id = good._id
  166. return {
  167. ...state,
  168. [_id]: { count: (state[_id]?.count || 0) - count, good }
  169. }
  170. }
  171. return state
  172. }
  173. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count, good })
  174. // const actionCartDelete = (good, count = 1) => ({ type: 'CART_DELETE', count, good })
  175. //под товаром сделать кнопку "купить"
  176. // buy.onclick = () => {
  177. // //debugger;
  178. // store.dispatch(actionCartAdd(good))
  179. // }
  180. //отрисовка количество где-то в хидере (на всех страницах)
  181. // store.subscribe(() => {
  182. // const cart = store.getState().cart
  183. // смочь в цикл/reduce по подсчету суммы количеств товаров и вывести куда-то в дом
  184. //(заготовка под кошик с количеством)
  185. // })
  186. function authReducer(state = { name: "Токен и все такое" }, action) { // { type, token }
  187. if (state === undefined) {
  188. //добавить в action token из localStorage, и проимитировать LOGIN (action.type = 'LOGIN')
  189. return {}
  190. }
  191. if (action.type === 'LOGIN') {
  192. console.log('ЛОГИН', action)
  193. //+localStorage
  194. //jwt_decode:
  195. //достать среднюю часть из токена (между точками)
  196. //atob
  197. //JSON.parse
  198. // return {token: action.token, payload: jwt_decode(action.jwt)}
  199. }
  200. if (action.type === 'LOGOUT') {
  201. console.log('ЛОГАУТ')
  202. //-localStorage
  203. //removeItem или clear
  204. //вернуть пустой объект
  205. return {}
  206. }
  207. return state
  208. }
  209. const reducers = {
  210. promise: promiseReducer,
  211. cart: cardReducer,
  212. auth: authReducer
  213. }
  214. const combineReducers = reducers => {
  215. return (state = {}, action) => {
  216. const newState = {}
  217. for (const [name, reducer] of Object.entries(reducers)) {
  218. // console.log(name, reducer)
  219. const newSubState = reducer(state[name], action)
  220. if (newSubState !== state[name]) {
  221. newState[name] = newSubState
  222. }
  223. }
  224. if (Object.keys(newState).length === 0) {
  225. return state
  226. }
  227. // {
  228. // ...
  229. // promise: state.promise,
  230. // cart: state.cart,
  231. // auth: state.auth,
  232. // promise: newState.promise
  233. // }
  234. return { ...state, ...newState }
  235. }
  236. }
  237. const store = createStore(combineReducers(reducers))
  238. ////
  239. //const store = createStore(reducer)
  240. const unsubscribe1 = store.subscribe(() => console.log(store.getState()))
  241. const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
  242. const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
  243. const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
  244. const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  245. //store.dispatch(actionPending('delay1000'))
  246. //delay(1000).then(payload => store.dispatch(actionResolved('delay1000', payload)),
  247. //error => store.dispatch(actionRejected('delay1000', error)))
  248. const actionPromise = (name, promise) =>
  249. async dispatch => {
  250. dispatch(actionPending(name))
  251. try {
  252. let payload = await promise
  253. dispatch(actionResolved(name, payload))
  254. return payload
  255. }
  256. catch (error) {
  257. dispatch(actionRejected(name, error))
  258. }
  259. }
  260. const getGQL = url =>
  261. (query, variables = {}) => fetch(url, {
  262. method: 'POST',
  263. headers: {
  264. // Accept: "application/json",
  265. "Content-Type": "application/json"
  266. //якщо в localStorage.authToken шото есть, то наверное это надо отправить с заголовком Authorization
  267. },
  268. body: JSON.stringify({ query, variables })
  269. }).then(res => res.json())
  270. let shopGQL = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql')
  271. const actionRootCategories = () =>
  272. actionPromise('rootCategories', shopGQL(`
  273. query cats($query:String){
  274. CategoryFind(query:$query){
  275. _id name
  276. }
  277. }
  278. `, { query: JSON.stringify([{ parent: null }]) }))
  279. const actionCategoryById = (_id) =>
  280. actionPromise('catById', shopGQL(`query catById($query:String){
  281. CategoryFindOne(query:$query){
  282. _id name goods{
  283. _id name price description images{
  284. url
  285. }
  286. }
  287. }
  288. }`, { query: JSON.stringify([{ _id }]) }))
  289. const actionGoodById = (_id) =>
  290. actionPromise('goodById', shopGQL(`query goodById($query:String){
  291. GoodFindOne(query:$query){
  292. _id name price description images {
  293. url
  294. }
  295. }
  296. }`, { query: JSON.stringify([{ _id }]) }))
  297. const actionGetToken = (login, password) =>
  298. actionPromise('getToken', shopGQL(`query login($login:String, $password:String){
  299. login(login: $login, password: $password)
  300. }`, { login, password }))
  301. // const actionGetOrders = () =>
  302. // actionPromise('orders', shopGQL(`query orders{
  303. // OrderFind(query:"[{}]"){
  304. // _id total orderGoods{
  305. // count good{
  306. // name
  307. // }
  308. // }
  309. // }
  310. // }`))
  311. const actionAuthLogin = token => ({ type: 'LOGIN', token })
  312. const actionFullLogin = (login, password) =>
  313. async dispatch => {
  314. let payload = await dispatch(actionGetToken(login, password))
  315. if (payload.data.login) {
  316. dispatch(actionAuthLogin(payload.data.login))
  317. }
  318. }
  319. //НАПИЛИТЬ actionFullRegister:
  320. // - actionRegister, который actionPromise
  321. // - если удачно, делаете сразу же actionFullLogin
  322. store.dispatch(actionRootCategories())
  323. store.dispatch(actionFullLogin('tst123', '123123'))
  324. function drawCart() {
  325. //цикл по отрисовке с картинками и редактирование количества/удалением товара
  326. //
  327. const cart = store.getState().cart
  328. // if (cart) {
  329. // main.innerText = ''
  330. // for (let {})
  331. // }
  332. // main.innerHTML = `<pre>${JSON.stringify(cart, null, 4)}</pre>`
  333. console.log("kart", cart)
  334. }
  335. function loadAnimationFunc() {
  336. main.innerHTML = ""
  337. let loadAnimationContainer = document.createElement('div')
  338. loadAnimationContainer.setAttribute('style', "width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;")
  339. main.append(loadAnimationContainer)
  340. let loadAnimation = document.createElement('img')
  341. loadAnimation.src = "https://image.flaticon.com/icons/png/512/2492/2492765.png"
  342. loadAnimation.setAttribute("style", "width: 50px; height: 50px; animation: load 1s linear infinite;")
  343. loadAnimation.animate([{ transform: 'rotate(360deg)' }], { duration: 1000, iterations: Infinity })
  344. loadAnimationContainer.append(loadAnimation)
  345. }
  346. window.onhashchange = () => {
  347. let { 1: route, 2: id } = location.hash.split('/')
  348. if (route === 'categories') {
  349. loadAnimationFunc()
  350. store.dispatch(actionCategoryById(id))
  351. }
  352. if (route === 'good') {
  353. loadAnimationFunc()
  354. store.dispatch(actionGoodById(id))
  355. }
  356. if (route === 'login') {
  357. // нарисовать форму логина, которая по OK делает
  358. // store.dispatch(actionFullLogin(login, password))
  359. }
  360. if (route === 'register') {
  361. // нарисовать форму регистрации, которая по OK делает
  362. // store.dispatch(actionFullRegister(login, password))
  363. }
  364. if (route === 'cart') {
  365. loadAnimationFunc()
  366. // store.dispatch(actionGetOrders())
  367. console.log("страница корзины")
  368. drawCart()
  369. //#/cart/
  370. // //нарисовать корзину с кнопочками добавления/удаления товаров
  371. // main.innerHTML = ""
  372. // //const cart = store.getState().cart
  373. // //смочь в цикл / reduce по подсчету суммы количеств товаров и вывести куда - то в дом
  374. // //(заготовка под кошик с количеством)
  375. }
  376. }
  377. function drawMainMenu() {
  378. //debugger;
  379. let cats = store.getState().promise.rootCategories.payload
  380. if (cats) { //Каждый раз дорисовываются в body
  381. aside.innerText = ''
  382. for (let { _id, name } of cats.data.CategoryFind) {
  383. let catA = document.createElement('a')
  384. catA.href = `#/categories/${_id}`
  385. catA.innerText = name
  386. console.log("cart")
  387. cartLink.href = `#/cart/`
  388. aside.append(catA)
  389. }
  390. }
  391. }
  392. store.subscribe(drawMainMenu)
  393. store.subscribe(() => {
  394. //debugger;
  395. const { 1: route, 2: id } = location.hash.split('/')
  396. if (route === 'categories') {
  397. const catById = store.getState().promise.catById?.payload
  398. if (catById) {
  399. main.innerText = ""
  400. // Вывести категорию(название)
  401. let h1 = document.createElement('h1')
  402. h1.setAttribute('style', 'width: 100%;')
  403. h1.innerText = catById.data.CategoryFindOne.name
  404. main.append(h1)
  405. // Вывести циклом товары со ссылками вида # / good / АЙДИШНИК
  406. for (let { _id, name, description, price, images } of catById.data.CategoryFindOne.goods) { //тута
  407. let divGoodA = document.createElement('div')
  408. divGoodA.setAttribute('style', "margin: 0 10px 10px 0; padding: 20px; width: 400px; border: 2px solid gray; display: flex; flex-direction: column; align-content: stretch ")
  409. divGoodA.onmousemove = () => divGoodA.style.borderColor = "#FF7373"
  410. divGoodA.onmouseout = () => divGoodA.style.borderColor = "gray"
  411. //background - color: #FF5319;
  412. let goodImg = document.createElement('img')
  413. goodImg.src = `http://shop-roles.asmer.fs.a-level.com.ua/${images[0].url}`
  414. let goodLink = document.createElement('a')
  415. goodLink.setAttribute('style', "flex: 1 1 auto; text-align: center; font-size: 22px")
  416. goodLink.href = `#/good/${_id}`
  417. goodLink.innerHTML = name
  418. let goodPrice = document.createElement('div')
  419. goodPrice.setAttribute('style', "font-size: 20px; font-weight: bold; text-align: center;")
  420. goodPrice.innerText = `Цена: ${price} грн`
  421. let goodButton = document.createElement('button')
  422. goodButton.setAttribute('style', "height: 50px; font-weight: bold; margin: 10px 0 0 0; border-radius: 30px; background-color: white;")
  423. goodButton.innerText = "Купить"
  424. goodButton.onclick = () => window.location.href = `#/good/${_id}`
  425. goodButton.onmousemove = () => goodButton.style.backgroundColor = "#FF5319"
  426. goodButton.onmouseout = () => goodButton.style.backgroundColor = "white"
  427. divGoodA.append(goodImg)
  428. divGoodA.append(goodLink)
  429. //divGoodA.append(goodDescription)
  430. divGoodA.append(goodPrice)
  431. divGoodA.append(goodButton)
  432. main.append(divGoodA)
  433. }
  434. // main.innerHTML = `<pre>${JSON.stringify(catById, null, 4)}</pre>`
  435. }
  436. }
  437. if (route === 'cart') {
  438. // нарисовать корзину с кнопочками добавления/удаления товаров
  439. //
  440. //const cart = store.getState().cart
  441. drawCart()
  442. }
  443. })
  444. store.subscribe(() => {
  445. // debugger;
  446. //когда появится actionGoodById и ссылки на товары это заработает
  447. const { 1: route, 2: id } = location.hash.split('/')
  448. if (route === 'good') {
  449. const goodById = store.getState().promise.goodById?.payload
  450. if (goodById) {
  451. let main = document.getElementById('main')
  452. main.innerHTML = ""
  453. let divGoodB = document.createElement('div')
  454. divGoodB.setAttribute('style', "margin: 0 10px 10px 25px; padding: 20px; display: flex; flex-direction: column;")
  455. let goodImg1 = document.createElement('img')
  456. goodImg1.src = `http://shop-roles.asmer.fs.a-level.com.ua/${goodById.data.GoodFindOne.images[0].url}`
  457. let goodLink1 = document.createElement('h1')
  458. goodLink1.setAttribute('style', "text-align: center; font-size: 22px")
  459. goodLink1.innerHTML = goodById.data.GoodFindOne.name
  460. let goodDescription1 = document.createElement('div')
  461. goodDescription1.setAttribute('style', "margin: 20px; ")
  462. goodDescription1.innerText = goodById.data.GoodFindOne.description
  463. let goodPrice1 = document.createElement('h1')
  464. goodPrice1.setAttribute('style', "font-size: 20px; font-weight: bold; text-align: center;")
  465. goodPrice1.innerText = `Цена: ${goodById.data.GoodFindOne.price} грн`
  466. let goodButton1 = document.createElement('button')
  467. goodButton1.setAttribute('style', "height: 50px; font-weight: bold; margin: 10px 0 0 0; border-radius: 30px; background-color: white;")
  468. goodButton1.innerText = "Купить"
  469. goodButton1.onmousemove = () => goodButton1.style.backgroundColor = "#FF5319"
  470. goodButton1.onmouseout = () => goodButton1.style.backgroundColor = "white"
  471. goodButton1.onclick = () => { store.dispatch(actionCartAdd(goodById.data.GoodFindOne)) }
  472. divGoodB.append(goodImg1)
  473. divGoodB.append(goodLink1)
  474. divGoodB.append(goodDescription1)
  475. divGoodB.append(goodPrice1)
  476. divGoodB.append(goodButton1)
  477. main.append(divGoodB)
  478. // вывести в main страницу товаров
  479. }
  480. }
  481. })
  482. </script>
  483. </body>
  484. </html>