App.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. import React, {useState, useEffect, useRef, Component, createRef} from 'react';
  2. import logoDefault from './logo.svg';
  3. import cartImg from './cart.svg';
  4. import 'bootstrap/dist/css/bootstrap.min.css';
  5. import './App.scss';
  6. import {Provider, connect} from 'react-redux';
  7. import {createStore, combineReducers, applyMiddleware} from 'redux';
  8. import thunk from 'redux-thunk';
  9. import createSagaMiddleware from 'redux-saga';
  10. import {all, takeEvery, put, call, take, select} from 'redux-saga/effects';
  11. import {Router, Route, Link, Redirect, Switch} from 'react-router-dom';
  12. import createHistory from "history/createBrowserHistory";
  13. const defaultRootCats = [
  14. {
  15. "_id": "5dc49f4d5df9d670df48cc64",
  16. "name": "Airconditions"
  17. },
  18. {
  19. "_id": "5dc458985df9d670df48cc47",
  20. "name": " Smartphones"
  21. },
  22. {
  23. "_id": "5dc4b2553f23b553bf354101",
  24. "name": "Крупная бытовая техника"
  25. },
  26. {
  27. "_id": "5dcac1b56d09c45440d14cf8",
  28. "name": "Макароны"
  29. }
  30. ]
  31. const defaultCat ={
  32. "subCategories": null,
  33. "_id": "5dc458985df9d670df48cc47",
  34. "name": " Smartphones",
  35. "goods": [
  36. {
  37. "_id": "61b105f9c750c12ba6ba4524",
  38. "name": "iPhone ",
  39. "price": 1200,
  40. "images": [
  41. {
  42. "url": "images/50842a3af34bfa28be037aa644910d07"
  43. }
  44. ]
  45. },
  46. {
  47. "_id": "61b1069ac750c12ba6ba4526",
  48. "name": "iPhone ",
  49. "price": 1000,
  50. "images": [
  51. {
  52. "url": "images/d12b07d983dac81ccad404582a54d8be"
  53. }
  54. ]
  55. },
  56. {
  57. "_id": "61b23f94c750c12ba6ba472a",
  58. "name": "name1",
  59. "price": 1214,
  60. "images": [
  61. {
  62. "url": null
  63. }
  64. ]
  65. },
  66. {
  67. "_id": "61b23fbac750c12ba6ba472c",
  68. "name": "smart",
  69. "price": 1222,
  70. "images": [
  71. {
  72. "url": "images/871f4e6edbf86c35f70b72dcdebcd8b2"
  73. }
  74. ]
  75. }
  76. ]
  77. }
  78. function jwtDecode(token) {
  79. try {
  80. let decoded = JSON.parse(atob(token.split('.')[1]))
  81. return decoded
  82. } catch (err) {
  83. console.log(err)
  84. }
  85. }
  86. function authReducer(state, {type, token}) {
  87. if (!state) {
  88. if (localStorage.authToken) {
  89. token = localStorage.authToken
  90. type = 'AUTH_LOGIN'
  91. } else {
  92. return {}
  93. }
  94. }
  95. if (type === 'AUTH_LOGIN') {
  96. let payload = jwtDecode(token)
  97. if (typeof payload === 'object') {
  98. localStorage.authToken = token
  99. return {
  100. ...state,
  101. token,
  102. payload
  103. }
  104. } else {
  105. return state
  106. }
  107. }
  108. if (type === 'AUTH_LOGOUT') {
  109. delete localStorage.authToken
  110. return {}
  111. }
  112. return state
  113. }
  114. const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token})
  115. const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
  116. function cartReducer (state={}, {type, good={}, count=1}) {
  117. if (Object.keys(state).length === 0 && localStorage.cart) {
  118. let currCart = JSON.parse(localStorage.cart)
  119. if (currCart && Object.keys(currCart).length !== 0) {
  120. state = currCart
  121. }
  122. }
  123. const {_id} = good
  124. const types = {
  125. CART_ADD() {
  126. count = +count
  127. if (!count) {
  128. return state
  129. }
  130. let newState = {
  131. ...state,
  132. [_id]: {good, count: (count + (state[_id]?.count || 0)) < 1 ? 1 : count + (state[_id]?.count || 0)}
  133. }
  134. localStorage.cart = JSON.stringify(newState)
  135. return newState
  136. },
  137. CART_CHANGE() {
  138. count = +count
  139. let newState = null
  140. // if (!count) {
  141. // return state
  142. // }
  143. newState = {
  144. ...state,
  145. [_id]: {good, count: count < 1 ? 1 : count}
  146. }
  147. localStorage.cart = JSON.stringify(newState)
  148. return newState
  149. },
  150. CART_REMOVE() {
  151. let { [_id]: removed, ...newState } = state
  152. localStorage.cart = JSON.stringify(newState)
  153. return newState
  154. },
  155. CART_CLEAR() {
  156. localStorage.cart = JSON.stringify({})
  157. return {}
  158. },
  159. }
  160. if (type in types) {
  161. return types[type]()
  162. }
  163. return state
  164. }
  165. const actionCartAdd = (good, count) => ({type: 'CART_ADD', good, count})
  166. const actionCartChange = (good, count) => ({type: 'CART_CHANGE', good, count})
  167. const actionCartRemove = (good) => ({type: 'CART_REMOVE', good})
  168. const actionCartClear = () => ({type: 'CART_CLEAR'})
  169. function promiseReducer(state={}, {type, status, payload, error, name}) {
  170. if (!state) {
  171. return {}
  172. }
  173. if (type === 'PROMISE') {
  174. return {
  175. ...state,
  176. [name]: {
  177. status: status,
  178. payload : payload,
  179. error: error,
  180. }
  181. }
  182. }
  183. return state
  184. }
  185. const actionPending = (name) => ({type: 'PROMISE', status: 'PENDING', name})
  186. const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
  187. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  188. const actionPromise = (name, promise) => (
  189. {type: 'PROMISE_START', name, promise}
  190. // async (dispatch) => {
  191. // dispatch(actionPending(name))
  192. // try {
  193. // let data = await promise
  194. // dispatch(actionResolved(name, data))
  195. // return data
  196. // }
  197. // catch(error){
  198. // dispatch(actionRejected(name, error))
  199. // }
  200. // }
  201. )
  202. const getGQL = url => (
  203. async (query, variables={}) => {
  204. let obj = await fetch(url, {
  205. method: 'POST',
  206. headers: {
  207. "Content-Type": "application/json",
  208. ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
  209. },
  210. body: JSON.stringify({ query, variables })
  211. })
  212. let a = await obj.json()
  213. if (!a.data && a.errors) {
  214. throw new Error(JSON.stringify(a.errors))
  215. } else {
  216. return a.data[Object.keys(a.data)[0]]
  217. }
  218. }
  219. )
  220. const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua/'
  221. const gql = getGQL(backURL + 'graphql')
  222. const actionOrder = () => (
  223. async (dispatch, getState) => {
  224. let {cart} = getState()
  225. const orderGoods = Object.entries(cart)
  226. .map(([_id, {good, count}]) => ({good: {_id}, count}))
  227. let result = await dispatch(actionPromise('order', gql(`
  228. mutation newOrder($order:OrderInput){
  229. OrderUpsert(order:$order)
  230. { _id total}
  231. }
  232. `, {order: {orderGoods}})))
  233. if (result?._id) {
  234. dispatch(actionCartClear())
  235. }
  236. }
  237. )
  238. const actionLogin = (login, password) => (
  239. actionPromise('login', gql(`query log($login: String, $password: String) {
  240. login(login: $login, password: $password)
  241. }`, {login, password}))
  242. )
  243. const actionFullLogin = (login, password) => (
  244. {type: 'FULL_LOGIN', login, password}
  245. // async (dispatch) => {
  246. // let token = await dispatch(actionLogin(login, password))
  247. // if (token) {
  248. // dispatch(actionAuthLogin(token))
  249. // }
  250. // }
  251. )
  252. const actionRegister = (login, password) => (
  253. actionPromise('register', gql(`mutation reg($user:UserInput) {
  254. UserUpsert(user:$user) {
  255. _id
  256. }
  257. }
  258. `, {user: {login, password}})
  259. )
  260. )
  261. const actionFullRegister = (login, password) => (
  262. async (dispatch) => {
  263. let regId = await dispatch(actionRegister(login, password))
  264. if (regId) {
  265. dispatch(actionFullLogin(login, password))
  266. }
  267. }
  268. )
  269. const actionRootCats = () => (
  270. actionPromise('rootCats', gql(`query {
  271. CategoryFind(query: "[{\\"parent\\":null}]"){
  272. _id name
  273. }
  274. }`))
  275. )
  276. const actionCatById = (_id) => (
  277. actionPromise('catById', gql(`query catById($q: String){
  278. CategoryFindOne(query: $q){
  279. _id name goods {
  280. _id name price images {
  281. url
  282. }
  283. }
  284. subCategories {
  285. _id name
  286. }
  287. }
  288. }`, {q: JSON.stringify([{_id}])}))
  289. )
  290. const actionGoodById = (_id) => (
  291. actionPromise('goodById', gql(`query goodById($q: String) {
  292. GoodFindOne(query: $q) {
  293. _id name price description images {
  294. url
  295. }
  296. }
  297. }`, {q: JSON.stringify([{_id}])}))
  298. )
  299. const actionGoodsByUser = (_id) => (
  300. actionPromise('goodByUser', gql(`query oUser($query: String) {
  301. OrderFind(query:$query){
  302. _id orderGoods{
  303. price count total good{
  304. _id name categories{
  305. name
  306. }
  307. images {
  308. url
  309. }
  310. }
  311. }
  312. owner {
  313. _id login
  314. }
  315. }
  316. }`,
  317. {query: JSON.stringify([{___owner: _id}])}))
  318. )
  319. const actionGoodFind = (word) => (
  320. actionPromise('goodFind', gql(`query goodById($q: String) {
  321. GoodFind(query: $q) {
  322. _id name price description images {
  323. url
  324. }
  325. }
  326. }`, {q: JSON.stringify([
  327. {
  328. $or: [{title: `/${word}/`}, {description: `/${word}/`}, {name: `/${word}/`}]
  329. },
  330. {
  331. sort: [{title: 1}]
  332. }
  333. ])
  334. }
  335. ))
  336. )
  337. // const store = createStore( combineReducers({ promise: promiseReducer,
  338. // auth: authReducer,
  339. // cart: cartReducer}),
  340. // applyMiddleware(thunk))
  341. const sagaMiddleware = createSagaMiddleware()
  342. const store = createStore( combineReducers({ promise: promiseReducer,
  343. auth: authReducer,
  344. cart: cartReducer}),
  345. applyMiddleware(sagaMiddleware))
  346. // аналог actionPromise
  347. function* promiseWorker(action) {
  348. const {name, promise} = action
  349. yield put(actionPending(name))
  350. try {
  351. let data = yield promise
  352. yield put(actionResolved(name, data))
  353. return data
  354. }
  355. catch(error){
  356. yield put(actionRejected(name, error))
  357. }
  358. }
  359. function* promiseWatcher() {
  360. // запуск worker для каждого экшна типа промис старт,
  361. // в обработчмк приходит результат функции с промис старт
  362. yield takeEvery('PROMISE_START', promiseWorker)
  363. }
  364. function* loginWorker(action) {
  365. const {login, password} = action
  366. // аналог вложенного диспатча вложенного санка
  367. let token = yield call(promiseWorker, actionLogin(login, password))
  368. if (token) {
  369. yield put(actionAuthLogin(token))
  370. }
  371. }
  372. function* loginWatcher() {
  373. yield takeEvery('FULL_LOGIN', loginWorker)
  374. }
  375. function* rootSaga() {
  376. yield all([
  377. promiseWatcher(),
  378. loginWatcher()
  379. ])
  380. }
  381. sagaMiddleware.run(rootSaga)
  382. store.subscribe(() => console.log(store.getState()))
  383. store.dispatch(actionRootCats())
  384. const Logo = ({logo=logoDefault}) => (
  385. <Link to='/' className="Logo">
  386. <img src={logo} />
  387. </Link>
  388. )
  389. const Koshik = ({cart}) => {
  390. let count = 0;
  391. let sum = Object.entries(cart).map(([, val]) => val.count)
  392. count = sum.reduce((a, b) => a + b, 0)
  393. //перебрать cart, посчитать суммарный count
  394. return (
  395. <Link to='/cart' className='Koshik'>
  396. <img src={cartImg} />
  397. {count}
  398. </Link>
  399. )
  400. }
  401. const CKoshik = connect(({cart}) => ({cart}))(Koshik)
  402. const GreetContainer = ({user: {id, login}}) => {
  403. if (id && login) {
  404. return (
  405. <div>
  406. <div>
  407. ПРИВЕТ, {login}
  408. </div>
  409. <Link to={`/orders/${id}`} >
  410. Мои заказы
  411. </Link>
  412. </div>
  413. )
  414. } else {
  415. return (<></>)
  416. }
  417. }
  418. const CGreetContainer = connect(state => ({user: state.auth.payload?.sub || {}}))(GreetContainer)
  419. const Header = ({logo=logoDefault}) => (
  420. <header className="bg-light">
  421. <div className="leftBlock">
  422. <Logo logo={logo} />
  423. </div>
  424. <div className="rightBlock">
  425. <CKoshik />
  426. <CGreetContainer />
  427. </div>
  428. </header>
  429. )
  430. const RootCategory = ({cat:{_id, name}={}}) => (
  431. <Link to={`/category/${_id}`}
  432. className="catBtn list-group-item list-group-item-action list-group-item-light">
  433. {name}
  434. </Link>
  435. )
  436. const RootCategories = ({cats=defaultRootCats}) => (
  437. <div className='RootCategories list-group linkList'>
  438. {cats.map(cat => <RootCategory cat={cat} />)}
  439. </div>
  440. )
  441. const CRootCategories = connect(state => ({cats: state.promise.rootCats?.payload || []}))(RootCategories)
  442. const LogoutBTn = ({onLogout}) => (
  443. <button className='logoutBtn btn btn-primary'
  444. onClick={() => onLogout()}>
  445. Logout
  446. </button>
  447. )
  448. const CLogoutBTn = connect(null, {onLogout: actionAuthLogout})(LogoutBTn)
  449. const Aside = () => (
  450. <aside className="bg-light">
  451. <div className='LoginBlock'>
  452. <Link to='/login' className='loginBtn btn btn-primary'>Login</Link>
  453. <Link to='/register' className='registerBtn btn btn-primary'>Register</Link>
  454. <CLogoutBTn />
  455. </div>
  456. <CRootCategories />
  457. </aside>
  458. )
  459. const Content = ({children}) => (
  460. <div className='Content'>
  461. {children}
  462. </div>
  463. )
  464. const SubCategory = ({cat:{_id, name}={}}) => (
  465. <Link to={`/category/${_id}`}
  466. className='list-group-item list-group-item-action list-group-item-primary linkItem'>
  467. {name}
  468. </Link>
  469. )
  470. const SubCategories = ({cats}) => (
  471. <div className="CatsList list-group linkList">
  472. {cats.map(cat => <SubCategory cat={cat} />)}
  473. </div>
  474. )
  475. const GoodCard = ({good:{_id, name, price, images}={}, onCartAdd}) => (
  476. <div className='GoodCard card'>
  477. {images && images[0] && images[0].url &&
  478. <img className='card-img-top' src={backURL + '/' + images[0].url} />}
  479. <div className="card-body">
  480. <h4 className="card-title">{name}</h4>
  481. <h5>{price} грн</h5>
  482. <button className='btn btn-primary'
  483. onClick={() => onCartAdd({_id, name, price, images})}>В корзину</button>
  484. <Link className='btn btn-success'
  485. to={`/good/${_id}`}>Подробнее</Link>
  486. </div>
  487. </div>
  488. )
  489. const CGoodCard = connect(null, {onCartAdd: actionCartAdd})(GoodCard)
  490. const Category = ({cat:{_id, name, goods, subCategories}=defaultCat}) => (
  491. <div className='Category'>
  492. <h1>{name}</h1>
  493. {subCategories && <SubCategories cats={subCategories} />}
  494. <div className='GoodCards'>
  495. {(goods || []).map(good => <CGoodCard good={good}/>)}
  496. </div>
  497. </div>
  498. )
  499. const CCategory = connect(state => ({cat: state.promise.catById?.payload}))(Category)
  500. const GoodInCart = ({item: {count, good: {_id, name, price, images}}, onCartChange, onCartRemove, onCartAdd}) => {
  501. const [goodCount, setGoodCount] = useState(count)
  502. return (
  503. <div className='CartCard card'>
  504. <div className="card-header">
  505. <h4 className="card-title">{name}</h4>
  506. </div>
  507. {images && images[0] && images[0].url &&
  508. <img className="card-img-top" src={backURL + '/' + images[0].url} />}
  509. <div className="card-body">
  510. <p>{count} шт</p>
  511. <p>{price} грн</p>
  512. <h6>Итого: {count*price} грн</h6>
  513. <button className="btn btn-success"
  514. onClick={() => { setGoodCount((count === 1 ) ? 1 : count - 1);
  515. onCartAdd({_id, name, price, images}, -1) }}>-</button>
  516. <input onBlur={() => onCartChange({_id, name, price, images}, goodCount)}
  517. onInput={(e) => setGoodCount(e.currentTarget.value)}
  518. // onInput={(e) => onCartChange({_id, name, price, images}, e.currentTarget.value)}
  519. value={goodCount} type="number"/>
  520. <button className="btn btn-success"
  521. onClick={() => { setGoodCount(count + 1);
  522. onCartAdd({_id, name, price, images}) }}>+</button>
  523. <button className="btn btn-success delBtn"
  524. onClick={() => onCartRemove({_id, name, price, images})}>Удалить</button>
  525. </div>
  526. </div>
  527. )
  528. }
  529. const CGoodInCart = connect(null, { onCartChange: actionCartChange,
  530. onCartRemove: actionCartRemove,
  531. onCartAdd: actionCartAdd})(GoodInCart)
  532. const Cart = ({cart, user, onCartClear, onOrderSend}) => {
  533. let total = 0
  534. if (cart.length !== 0) {
  535. let countes = Object.entries(cart).map(([, val]) => val.count)
  536. let prices = Object.entries(cart).map(([, val]) => val.good.price)
  537. let sum = countes.map((count, index) => prices[index] * count)
  538. total = sum.reduce((a, b) => a + b)
  539. }
  540. return (
  541. <div className='Cart'>
  542. {cart.length === 0 ? <h5>Корзина пуста</h5> :
  543. <button className='btn btn-primary'
  544. onClick={() => onCartClear()}>
  545. Очистить корзину
  546. </button>}
  547. <div className='CartBlock'>
  548. {cart.map(item => <CGoodInCart item={item}/>)}
  549. </div>
  550. {cart.length === 0 ? <></> :
  551. <>
  552. <h5>Всего к оплате: {total} грн</h5>
  553. <button className='btn btn-primary'
  554. onClick={() => onOrderSend()}
  555. disabled={user ? false : true}>
  556. Оформить заказ
  557. </button>
  558. </>}
  559. </div>
  560. )
  561. }
  562. const CCart = connect(state => ({ cart: Object.values(state.cart) || [],
  563. user: state.auth.payload || null}),
  564. { onCartClear: actionCartClear,
  565. onOrderSend: actionOrder})(Cart)
  566. const LoginForm = ({onLogin}) => {
  567. const [login, setLogin] = useState("")
  568. const [pass, setPass] = useState("")
  569. return (
  570. <div className='LoginForm form'>
  571. <div className="mb-3">
  572. <label className="form-label">Логин</label>
  573. <input className="form-input form-control"
  574. type="text"style={ {backgroundColor: (login.length === 0) ? '#f00b' : '#fff'} }
  575. value={login} onChange={(e) => setLogin(e.target.value)}/>
  576. </div>
  577. <div className="mb-3">
  578. <label className="form-label">Пароль</label>
  579. <input className="form-input form-control"
  580. type="password" style={ {backgroundColor: (pass.length === 0) ? '#f00b' : '#fff'} }
  581. value={pass} onChange={(e) => setPass(e.target.value)}/>
  582. </div>
  583. <button className="btn btn-primary"
  584. onClick={() => { onLogin(login,pass);
  585. setPass('')
  586. }}
  587. disabled={(login.length !== 0 && pass.length !== 0) ? false : true}>Войти</button>
  588. </div>
  589. )
  590. }
  591. const CLoginForm = connect(null, {onLogin: actionFullLogin})(LoginForm)
  592. const RegisterForm = ({onRegister}) => {
  593. const [login, setLogin] = useState("")
  594. const [pass, setPass] = useState("")
  595. return (
  596. <div className='RegisterForm form'>
  597. <div className="mb-3">
  598. <label className="form-label">Логин</label>
  599. <input className="form-input form-control"
  600. type="text" style={ {backgroundColor: (login.length === 0) ? '#f00b' : '#fff'} }
  601. value={login} onChange={(e) => setLogin(e.target.value)}/>
  602. </div>
  603. <div className="mb-3">
  604. <label className="form-label">Пароль</label>
  605. <input className="form-input form-control"
  606. type="password" style={ {backgroundColor: (pass.length === 0) ? '#f00b' : '#fff'} }
  607. value={pass} onChange={(e) => setPass(e.target.value)}/>
  608. </div>
  609. <button className="btn btn-primary"
  610. onClick={() => { onRegister(login,pass);
  611. setPass('')
  612. }}
  613. disabled={(login.length !== 0 && pass.length !== 0) ? false : true}>Зарегистрироваться</button>
  614. </div>
  615. )
  616. }
  617. const CRegisterForm = connect(null, {onRegister: actionFullRegister})(RegisterForm)
  618. const PageMain = () => (
  619. <h1>Главная страница</h1>
  620. )
  621. const PageCategory = ({match:{params:{_id}}, getData, history}) => {
  622. useEffect(() => {
  623. getData(_id)
  624. },[_id])
  625. return (
  626. <CCategory />
  627. )
  628. }
  629. const CPageCategory = connect(null, {getData: actionCatById})(PageCategory)
  630. const ThisGood = ({good:{_id, name, price, images, description}={}, onCartAdd}) => (
  631. <div className='GoodPage'>
  632. <h2>{name}</h2>
  633. {images && images[0] && images[0].url && <img src={backURL + '/' + images[0].url} />}
  634. <div>
  635. <h6>{description}</h6>
  636. <strong>Цена - {price} грн</strong>
  637. </div>
  638. <button className="btn btn-success"
  639. onClick={() => onCartAdd({_id, name, price, images})}>В корзину</button>
  640. </div>
  641. )
  642. const CThisGood = connect( state => ({good: state.promise.goodById?.payload}),
  643. {onCartAdd: actionCartAdd})(ThisGood)
  644. const PageGood = ({match:{params:{_id}}, getData}) => {
  645. useEffect(() => {
  646. getData(_id)
  647. },[_id])
  648. return (
  649. <>
  650. <CThisGood />
  651. </>
  652. )
  653. }
  654. const CPageGood = connect(null, {getData: actionGoodById})(PageGood)
  655. // на текущий момент функция отображает только 100 последних заказов
  656. // подсчет потраченных средств нужно реализовать для всех заказов
  657. // но при этом сразу отображать только 100 последних
  658. const OrderedGood = ({goodOrder:{price, count, total, good}={}}) => {
  659. if (price !== null && count !== null && total !== null && good !== null) {
  660. const {_id, name, images} = good
  661. return (
  662. <div className='OrderCard card'>
  663. {images && images[0] && images[0].url &&
  664. <img className='card-img-top' src={backURL + '/' + images[0].url} />}
  665. <div className="card-body">
  666. <h4 className="card-title">{name}</h4>
  667. <h6>
  668. Куплено: {count} по {price} грн.
  669. </h6>
  670. <h6>
  671. Итого: {total} грн
  672. </h6>
  673. <Link className="btn btn-primary"
  674. to={`/good/${_id}`}>Подробнее</Link>
  675. </div>
  676. </div>
  677. )
  678. } else {
  679. return <></>
  680. }
  681. }
  682. const Orders = ({orders}) => (
  683. <div className='Orders'>
  684. {/* <h3>Всего потрачено: {totalMoney} грн</h3> */}
  685. <div className='OrderCards'>
  686. { orders.map(order =>
  687. (order.orderGoods ? order.orderGoods: []).map(goodOrder =>
  688. <OrderedGood goodOrder={goodOrder}/>))}
  689. </div>
  690. </div>
  691. )
  692. const COrders = connect(state => ({orders: state.promise.goodByUser?.payload || []}))(Orders)
  693. const PageOrders = ({match:{params:{_id}}, getData}) => {
  694. useEffect(() => {
  695. getData(_id)
  696. },[_id])
  697. return (
  698. <>
  699. <COrders />
  700. </>
  701. )
  702. }
  703. const CPageOrders = connect(null, {getData: actionGoodsByUser})(PageOrders)
  704. const Page404 = () => (
  705. <h1>PAGE НЭМА </h1>
  706. )
  707. const Main = () => (
  708. <main>
  709. <Aside />
  710. <Content>
  711. <Redirect from="/main" to="/" />
  712. <Switch>
  713. <Route path="/" component={PageMain} exact />
  714. <Route path="/category/:_id" component={CPageCategory} />
  715. <Route path="/good/:_id" component={CPageGood} />
  716. <Route path="/cart" component={CCart} />
  717. <Route path="/login" component={CLoginForm} />
  718. <Route path="/register" component={CRegisterForm} />
  719. <Route path="/orders/:_id" component={CPageOrders} />
  720. <Route path="*" component={Page404} />
  721. </Switch>
  722. </Content>
  723. </main>
  724. )
  725. const Footer = ({logo=logoDefault}) => (
  726. <footer>
  727. <Logo logo={logo} />
  728. </footer>
  729. )
  730. const history = createHistory()
  731. function App() {
  732. return (
  733. <Router history={history}>
  734. <Provider store={store}>
  735. <div className="App">
  736. <Header />
  737. <Main />
  738. <Footer />
  739. </div>
  740. </Provider>
  741. </Router>
  742. )
  743. }
  744. export default App;