App.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. import logoDefault from './logo.svg';
  2. import './App.scss';
  3. import {Provider, connect} from 'react-redux';
  4. import {createStore, combineReducers, applyMiddleware} from 'redux';
  5. import thunk from 'redux-thunk';
  6. import { BrowserRouter as Router, Route, Routes, Link} from 'react-router-dom';
  7. function authReducer(state, { type, token }) {
  8. if (!state) {
  9. if (localStorage.authToken) {
  10. type = 'AUTH_LOGIN'
  11. token = localStorage.authToken
  12. } else state = {}
  13. }
  14. if (type === 'AUTH_LOGIN') {
  15. localStorage.setItem('authToken', token)
  16. let payload = jwtDecode(token)
  17. if (typeof payload === 'object') {
  18. return {
  19. ...state,
  20. token,
  21. payload
  22. }
  23. } else return state
  24. }
  25. if (type === 'AUTH_LOGOUT') {
  26. localStorage.removeItem('authToken')
  27. return {}
  28. }
  29. return state
  30. }
  31. const jwtDecode = token => {
  32. try {
  33. let arrToken = token.split('.')
  34. let base64Token = atob(arrToken[1])
  35. return JSON.parse(base64Token)
  36. }
  37. catch (e) {
  38. console.log('Лажа, Бро ' + e);
  39. }
  40. }
  41. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
  42. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
  43. const actionLogin = (login, password) =>
  44. actionPromise('login', gql(`query NameForMe1($login:String, $password:String){
  45. login(login:$login, password:$password)
  46. }`, { login, password }))
  47. function cartReducer(state = {}, { type, good = {}, count = 1 }) {
  48. const { _id } = good
  49. const types = {
  50. CART_ADD() {
  51. count = +count
  52. if (!count) {
  53. return state
  54. }
  55. return {
  56. ...state,
  57. [_id]: {good, count: (count + (state[_id]?.count || 0)) < 0 ? 0 : count + (state[_id]?.count || 0)}
  58. }
  59. },
  60. CART_CHANGE() {
  61. count = +count
  62. if (!count) {
  63. return state
  64. }
  65. return {
  66. ...state,
  67. [_id]: {good, count: count < 0 ? 0 : count}
  68. }
  69. },
  70. CART_REMOVE() {
  71. let { [_id]: remove, ...newState } = state
  72. return {
  73. ...newState
  74. }
  75. },
  76. CART_CLEAR() {
  77. return {}
  78. },
  79. }
  80. if (type in types) {
  81. return types[type]()
  82. }
  83. return state
  84. }
  85. const actionCartAdd = (good, count) => ({type: 'CART_ADD', good, count})
  86. const actionCartChange = (good, count) => ({type: 'CART_CHANGE', good, count})
  87. const actionCartRemove = (good) => ({type: 'CART_REMOVE', good})
  88. const actionCartClear = () => ({type: 'CART_CLEAR'})
  89. function promiseReducer(state = {}, { type, status, payload, error, name }) {
  90. if (type === 'PROMISE') {
  91. return {
  92. ...state,
  93. [name]: { status, payload, error }
  94. }
  95. }
  96. return state;
  97. }
  98. const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
  99. const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
  100. const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
  101. const actionPromise = (name, promise) => (
  102. async dispatch => {
  103. dispatch(actionPending(name))
  104. try {
  105. let data = await promise
  106. dispatch(actionResolved(name, data))
  107. return data
  108. }
  109. catch (error) {
  110. dispatch(actionRejected(name, error))
  111. }
  112. }
  113. )
  114. const actionRootCats = () => (
  115. actionPromise('rootCats', gql(`query {
  116. CategoryFind(query: "[{\\"parent\\":null}]"){
  117. _id name
  118. }
  119. }`))
  120. )
  121. const actionCatById = (_id) => (
  122. actionPromise('catById', gql(`query catById($q: String){
  123. CategoryFindOne(query: $q){
  124. _id name goods {
  125. _id name price images {
  126. url
  127. }
  128. }
  129. subCategories {
  130. _id name
  131. }
  132. }
  133. }`, {q: JSON.stringify([{_id}])}))
  134. )
  135. const getGQL = url => (
  136. async (query, variables = {}) => {
  137. let obj = await fetch(url, {
  138. method: 'POST',
  139. headers: {
  140. "Content-Type": "application/json",
  141. Authorization: localStorage.authToken ? 'Bearer ' + localStorage.authToken : {},
  142. },
  143. body: JSON.stringify({ query, variables })
  144. })
  145. let a = await obj.json()
  146. if (!a.data && a.errors)
  147. throw new Error(JSON.stringify(a.errors))
  148. return a.data[Object.keys(a.data)[0]]
  149. }
  150. )
  151. const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
  152. const gql = getGQL(backURL + '/graphql');
  153. const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}),
  154. applyMiddleware(thunk))
  155. store.subscribe(() => console.log(store.getState()))
  156. store.dispatch(actionRootCats())
  157. store.dispatch(actionCatById('5dc49f4d5df9d670df48cc64'))
  158. const Logo = ({logo}) => (
  159. <a href='#' className="Logo">
  160. <img src={logo} />
  161. </a>
  162. )
  163. const Koshik = ({cart}) => {
  164. let count = 0;
  165. console.log(cart)
  166. let sum = Object.entries(cart).map(([, val]) => val.count)
  167. count = sum.reduce((a, b) => a + b, 0)
  168. return (
  169. <Link to='/cart' className='Koshik'>{count}</Link>
  170. )
  171. }
  172. const CKoshik = connect(({cart}) => ({cart}))(Koshik)
  173. const Header = ({logo=logoDefault}) => (
  174. <header>
  175. <CKoshik />
  176. <Logo logo={logo} />
  177. </header>
  178. )
  179. const defaultRootCats = [
  180. {_id: '5dc49f4d5df9d670df48cc64', name: 'Airconditions'},
  181. {_id: '5dc458985df9d670df48cc47', name: ' Smartphones'},
  182. {_id: '5dc4b2553f23b553bf354101', name: 'Крупная бытовая техника'},
  183. {_id: '5dcac1b56d09c45440d14cf8', name: 'Макароны'},
  184. {_id: '5dcac6cf6d09c45440d14cfd', name: 'Drinks'},
  185. {_id: '5dcacaeb6d09c45440d14d04', name: 'Салаты'},
  186. {_id: '61715b92ef4e1b3e3b67703c', name: 'Приятный бонус'},
  187. ]
  188. const RootCategory = ({cat:{_id, name}={}}) => (
  189. <li>
  190. <Link to='/category' >{name}</Link>
  191. {/* <a href={`#/category/${_id}`}>{name}</a> */}
  192. </li>
  193. )
  194. const RootCategories = ({cats=defaultRootCats}) => (
  195. <ul className='RootCategories'>
  196. {cats.map(cat => <RootCategory cat={cat}/>)}
  197. </ul>
  198. )
  199. const CRootCategories = connect(state => ({cats: state.promise.rootCats?.payload || []}))(RootCategories)
  200. const Aside = ({}) => (
  201. <aside>
  202. <CRootCategories/>
  203. </aside>
  204. )
  205. const Content = ({children}) => (
  206. <div className='Content'>
  207. {children}
  208. </div>
  209. )
  210. const SubCategories = ({cats}) => (
  211. <></>
  212. )
  213. const GoodCard = ({good:{_id, name, price, images}={}, onCartAdd}) => (
  214. <div className='GoodCard'>
  215. <h2>{name}</h2>
  216. {images && images[0] && images[0].url && <img src={backURL + '/' + images[0].url} />}
  217. <strong>{price}</strong>
  218. <button onClick={() => onCartAdd({_id, name, price, images})}>+</button>
  219. </div>
  220. )
  221. const CGoodCard = connect(null, {onCartAdd: actionCartAdd})(GoodCard)
  222. const Category = ({cat:{_id, name, goods, subCategories}}) => (
  223. <div className="Category">
  224. <h1>{name}</h1>
  225. {subCategories && <SubCategories cats={subCategories}/>}
  226. {(goods || []).map(good => <CGoodCard good={good}/>)}
  227. </div>
  228. )
  229. const CCategory = connect(state => ({cat: state.promise.catById?.payload || []}))(Category)
  230. const GoodInCart = ({item: {count, good: {_id, name, price, images}}, onCartChange, onCartRemove, onCartAdd}) => (
  231. <div className='GoodCard'>
  232. <h2>{name}</h2>
  233. {images && images[0] && images[0].url && <img src={backURL + '/' + images[0].url} />}
  234. <strong>{count}</strong>
  235. <button onClick={() => onCartAdd({_id, name, price, images}, -1)}>-</button>
  236. <input onInput={(e) => onCartChange({_id, name, price, images}, e.currentTarget.value)} value={count} type="number"/>
  237. <button onClick={() => onCartAdd({_id, name, price, images})}>+</button>
  238. <button onClick={() => onCartRemove({_id, name, price, images})}>Удалить</button>
  239. </div>
  240. )
  241. const CGoodInCart = connect(null, {onCartChange: actionCartChange, onCartRemove: actionCartRemove, onCartAdd: actionCartAdd})(GoodInCart)
  242. const Cart = ({cartQ, onCartClear}) => (
  243. <div className='Cart'>
  244. {cartQ.length === 0 ? <></> : <button onClick={() => onCartClear()}>Очистить корзину</button>}
  245. {cartQ.map(item => <CGoodInCart item={item}/>)}
  246. {cartQ.length === 0 ? <></> : <button>Оформить заказ</button>}
  247. </div>
  248. )
  249. const CCart = connect(state => ({cartQ: Object.values(state.cart) || []}), {onCartClear: actionCartClear})(Cart)
  250. const Main = ({}) => (
  251. <main>
  252. <Aside />
  253. <Content>
  254. <Routes>
  255. <Route path='/cart' element={<CCart />}/>
  256. <Route path='/category' element={<CCategory/>}/>
  257. </Routes>
  258. </Content>
  259. </main>
  260. )
  261. const Footer = ({logo=logoDefault}) => (
  262. <footer>
  263. <Logo logo={logo} />
  264. </footer>
  265. )
  266. const JSONTest = ({data}) => (
  267. <pre>
  268. {JSON.stringify(data, null, 4)}
  269. {Math.random() > 0.5 ? <h1>qqqqqq</h1> : <h2>qq</h2>}
  270. {Math.random() > 0.5 && <h1>zzzz</h1>}
  271. </pre>
  272. )
  273. const ReduxJSON = connect(state => ({data: state}))(JSONTest)
  274. const ListItem = ({item}) => (
  275. <li>{item}</li>
  276. )
  277. const List = ({data=["пиво", "чипсы", "сиги",]}) => (
  278. <ul>
  279. {/* {[ <ListItem item={data[0]}/>,
  280. <ListItem item={data[1]}/>,
  281. <ListItem item={data[2]}/>
  282. ]} */}
  283. {data.map(item => <ListItem item={item}/>)}
  284. </ul>
  285. )
  286. function App() {
  287. return (
  288. <Provider store={store}>
  289. <Router>
  290. <div className="App">
  291. <Header />
  292. <Main />
  293. {/* <List /> */}
  294. {/* <JSONTest data={[1,2,{a:5}]} /> */}
  295. {/* <ReduxJSON /> */}
  296. <Footer />
  297. </div>
  298. </Router>
  299. </Provider>
  300. );
  301. }
  302. export default App;