App.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import logo from './logo.svg';
  2. import './App.scss';
  3. import thunk from 'redux-thunk';
  4. import {createStore, combineReducers, applyMiddleware} from 'redux';
  5. import {Provider, connect} from 'react-redux';
  6. const actionPending = name => ({type: 'PROMISE', status: 'PENDING', name})
  7. const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
  8. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  9. const actionPromise = (name, promise) =>
  10. async dispatch => {
  11. dispatch(actionPending(name)) // 1. {delay1000: {status: 'PENDING'}}
  12. try{
  13. let payload = await promise
  14. dispatch(actionResolved(name, payload))
  15. return payload
  16. }
  17. catch(error){
  18. dispatch(actionRejected(name, error))
  19. }
  20. }
  21. const getGQL = url =>
  22. (query, variables = {}) =>
  23. fetch(url, {
  24. method: 'POST',
  25. headers: {
  26. "Content-Type": "application/json",
  27. ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } :
  28. {})
  29. },
  30. body: JSON.stringify({ query, variables })
  31. })
  32. .then(res => res.json())
  33. .then(data => {
  34. if (data.errors && !data.data)
  35. throw new Error(JSON.stringify(data.errors))
  36. return data.data[Object.keys(data.data)[0]]
  37. })
  38. const backendURL = "http://shop-roles.asmer.fs.a-level.com.ua"
  39. const gql = getGQL(backendURL + '/graphql')
  40. function jwtDecode(token) {
  41. try {
  42. let decoded = token.split('.')
  43. decoded = decoded[1]
  44. decoded = atob(decoded)
  45. decoded = JSON.parse(decoded)
  46. return decoded
  47. } catch (e) {
  48. return;
  49. }
  50. }
  51. const actionRootCats = () =>
  52. actionPromise('rootCats', gql(`query {
  53. CategoryFind(query: "[{\\"parent\\":null}]"){
  54. _id name
  55. }
  56. }`))
  57. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', good, count })
  58. const actionCartRemove = (good, count = 1) => ({ type: 'CART_REMOVE', good, count })
  59. const actionCartChange = (good, count = 1) => ({ type: 'CART_CHANGE', good, count })
  60. const actionCartClear = (good, count = 1) => ({ type: 'CART_CLEAR', good, count })
  61. function cartReducer(state = {}, { type, good={}, count=1}){
  62. const {_id } = good
  63. const types = {
  64. CART_ADD() {
  65. return {
  66. ...state,
  67. [_id]: { good, count: count + (state[_id]?.count || 0)}
  68. }
  69. },
  70. CART_REMOVE(){
  71. let newState = { ...state }
  72. delete newState[_id]
  73. return {
  74. ...newState
  75. }
  76. },
  77. CART_CHANGE(){
  78. return {
  79. ...state,
  80. [_id]:{good, count}
  81. }
  82. },
  83. CART_CLEAR(){
  84. return {}
  85. },
  86. }
  87. if (type in types)
  88. return types[type]()
  89. return state
  90. }
  91. function authReducer(state, {type, token}){
  92. if (!state) {
  93. if (localStorage.authToken) {
  94. type = 'AUTH_LOGIN'
  95. token = localStorage.authToken
  96. } else {
  97. return {}
  98. }
  99. }
  100. if (type === 'AUTH_LOGIN') {
  101. let auth = jwtDecode(token)
  102. if (auth) {
  103. localStorage.authToken = token
  104. return { token, payload: auth }
  105. }
  106. }
  107. if (type === 'AUTH_LOGOUT') {
  108. localStorage.authToken = ''
  109. return {}
  110. }
  111. return state
  112. }
  113. function promiseReducer(state={}, {type, name, status, payload, error}){
  114. if (type === 'PROMISE'){
  115. return {
  116. ...state,
  117. [name]:{status, payload, error}
  118. }
  119. }
  120. return state
  121. }
  122. const actionAuthLogin = (token) => ({ type: 'AUTH_LOGIN', token })
  123. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
  124. const actionLogin = (login = 'tst', password = '123') =>
  125. actionPromise('login', gql(`query ($login:String, $password:String){ login(login:$login, password:$password)}`, { 'login': login, 'password': password }))
  126. const actionFullLogin = (login = 'tst', password = '123') =>
  127. async dispatch => {
  128. let token = await dispatch(actionLogin(login, password))
  129. if (token) {
  130. dispatch(actionAuthLogin(token))
  131. }
  132. }
  133. const actionRegister = (login = 'tst', password = '123') =>
  134. actionPromise('login', gql(`mutation reg($login:String, $password:String) {
  135. UserUpsert(user:{login:$login, password:$password, nick:$login}){
  136. _id login
  137. }
  138. }`, { 'login': login, 'password': password }))
  139. const actionFullRegister = (login = 'tst', password = '123') =>
  140. async dispatch => {
  141. await dispatch(actionRegister(login, password))
  142. await dispatch(actionFullLogin(login, password))
  143. }
  144. const actionCatById = (_id) =>
  145. actionPromise('catById', gql(`query ($q: String){
  146. CategoryFindOne(query: $q){
  147. _id name goods {
  148. _id name price images {
  149. url
  150. }
  151. }
  152. subCategories {
  153. _id name
  154. }
  155. }
  156. }`, { q: JSON.stringify([{ _id }]) }))
  157. const actionGoodById = (_id) =>
  158. actionPromise('goodById', gql(`query ($good:String) {
  159. GoodFindOne(query:$good) {
  160. _id name price images {
  161. url
  162. }
  163. }
  164. }`, { good: JSON.stringify([{ _id }]) }))
  165. const store = createStore(combineReducers({promise: promiseReducer,
  166. auth: authReducer,
  167. cart: cartReducer}), applyMiddleware(thunk))
  168. store.subscribe(() => console.log(store.getState()))
  169. store.dispatch(actionRootCats())
  170. store.dispatch(actionCatById('5dc458985df9d670df48cc47'))
  171. const Logo = () =>
  172. <img src={logo} className="Logo" alt="logo" />
  173. const Header = () =>
  174. <header><Logo /></header>
  175. const Navbar = () =>
  176. <nav className='Navbar'>
  177. <CKoshik/>
  178. </nav>
  179. const CategoryListItem = ({_id, name}) =>
  180. <li className='CatLink'>
  181. <a href={`#/category/${_id}`}>{name}</a>
  182. </li>
  183. const CategoryList = ({cats}) =>
  184. <ul>{cats.map((item) => <CategoryListItem {...item}/>)}</ul>
  185. const CCategoryList = connect(state => ({cats:state.promise.rootCats?.payload || []}))(CategoryList)
  186. const Aside = () =>
  187. <aside><CCategoryList /></aside>
  188. const GoodCard = ({good:{_id, name, price, images}, onAdd}) =>
  189. <li className='GoodCard'>
  190. <h2>{name}</h2>
  191. {images && images[0] && images[0].url && <img className='GoodImg' src={backendURL + '/' + images[0].url} />}
  192. <br/>
  193. <strong>Цена: {price}</strong>
  194. <br/>
  195. <button onClick={() => onAdd({_id, name, price, images})}>Добавить в корзину</button>
  196. </li>
  197. const CGoodCard = connect(null, {onAdd: actionCartAdd})(GoodCard)
  198. const Category = ({cat:{name, goods=[]}={}}) =>
  199. <div className='Category'>
  200. <h1>{name}</h1>
  201. <ul>{goods.map(good => <CGoodCard good={good} />)}</ul>
  202. </div>
  203. const CCategory = connect(state => ({cat:state.promise.catById?.payload || {}}))(Category)
  204. const Koshik = ({cart}) => {
  205. let goodsInCart = cart
  206. let allGoodsInCart = 0
  207. for (let key in goodsInCart) {
  208. allGoodsInCart += goodsInCart[key].count
  209. }
  210. return (
  211. <div className='KoshikCnt item'>
  212. <h3>Корзина: {allGoodsInCart}</h3>
  213. </div>
  214. )
  215. }
  216. const CKoshik = connect(({cart}) => ({cart}))(Koshik)
  217. const CartItem = ({cart:{_id, name, price, images}, count: {count}, onChange, onRemove}) => {
  218. console.log('good', _id)
  219. return(
  220. <li className='GoodCard'>
  221. <h2>{name}</h2>
  222. {images && images[0] && images[0].url && <img className='GoodImg' src={backendURL + '/' + images[0].url} />}
  223. <br/>
  224. <strong>Цена: {price * count}</strong>
  225. &nbsp;
  226. <label>Кол-во покупки: <input type="number" value={count} onInput={() => onChange({_id, name, price, images})}/></label>
  227. <br/>
  228. <button>Заказать</button>
  229. <button onClick={() => onRemove({_id, name, price, images})}>Удалить заказ[X]</button>
  230. </li>
  231. )
  232. }
  233. const CCartItem = connect(null, {onChange: actionCartChange, onRemove: actionCartRemove})(CartItem)
  234. const Cart = ({cart}) => {
  235. let cartArr = []
  236. for(let item in cart) {
  237. cartArr.push(cart[item])
  238. }
  239. console.log('cartarr',cartArr)
  240. return(
  241. <div>
  242. <h1>Корзина</h1>
  243. <ul>{cartArr.map(item => <CCartItem cart={item.good} count={item} />)}</ul>
  244. </div>
  245. )
  246. }
  247. const CCart = connect(state => ({cart:state.cart}))(Cart)
  248. //const CCart = connect(забрать из редакса корзину положить в пропс cart,
  249. //дать компоненту onCartChange и onCartRemove с соответствующими actionCreator)(Cart)
  250. const Main = () =>
  251. <main>
  252. <Aside />
  253. <Content>
  254. <CCategory />
  255. ТУТ ДЛОЖЕН бЫТЬ ЦКАРТ
  256. <CCart />
  257. {/* <Cart /> */}
  258. </Content>
  259. </main>
  260. const Content = ({children}) =>
  261. <div className="Content">{children}</div>
  262. const Footer = () =>
  263. <footer><Logo /></footer>
  264. function App() {
  265. return (
  266. <Provider store={store}>
  267. <div className="App">
  268. <Header />
  269. <Navbar />
  270. <Main />
  271. <Footer />
  272. </div>
  273. </Provider>
  274. );
  275. }
  276. export default App;