App.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. import logoDefault from './logo.svg';
  2. import './App.css';
  3. import {Provider, connect} from 'react-redux';
  4. import {createStore, combineReducers, applyMiddleware} from 'redux';
  5. import thunk from 'redux-thunk';
  6. const jwtDecode = token => {
  7. try {
  8. let arrToken = token.split('.')
  9. let base64Token = atob(arrToken[1])
  10. return JSON.parse(base64Token)
  11. }
  12. catch (e) {
  13. console.log('Лажа, Бро ' + e);
  14. }
  15. }
  16. function authReducer(state, { type, token }) {
  17. if (!state) {
  18. if (localStorage.authToken) {
  19. type = 'AUTH_LOGIN'
  20. token = localStorage.authToken
  21. } else state = {}
  22. }
  23. if (type === 'AUTH_LOGIN') {
  24. localStorage.setItem('authToken', token)
  25. let payload = jwtDecode(token)
  26. if (typeof payload === 'object') {
  27. return {
  28. ...state,
  29. token,
  30. payload
  31. }
  32. } else return state
  33. }
  34. if (type === 'AUTH_LOGOUT') {
  35. localStorage.removeItem('authToken')
  36. return {}
  37. }
  38. return state
  39. }
  40. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
  41. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
  42. const actionLogin = (login, password) =>
  43. actionPromise('login', gql(`query NameForMe1($login:String, $password:String){
  44. login(login:$login, password:$password)
  45. }`, { login, password }))
  46. function cartReducer(state = {}, { type, good = {}, count = 1 }) {
  47. const { _id } = good
  48. const types = {
  49. CART_ADD() {
  50. count = +count
  51. if (!count) return state
  52. return {
  53. ...state,
  54. [_id]: {
  55. good,
  56. count: count + (state[_id]?.count || 0)
  57. }
  58. }
  59. },
  60. CART_CHANGE() {
  61. count = +count
  62. if (!count) return state
  63. return {
  64. ...state,
  65. [_id]: {
  66. good,
  67. count: count
  68. }
  69. }
  70. },
  71. CART_REMOVE() {
  72. let { [_id]: remove, ...newState } = state
  73. return {
  74. ...newState
  75. }
  76. },
  77. CART_CLEAR() {
  78. return {}
  79. },
  80. }
  81. if (type in types) {
  82. return types[type]()
  83. }
  84. return state
  85. }
  86. const actionCartAdd = (good, count=1) => ({type: "CART_ADD", good, count});
  87. function promiseReducer(state = {}, { type, status, payload, error, name }) {
  88. if (type === 'PROMISE') {
  89. return {
  90. ...state,
  91. [name]: { status, payload, error }
  92. }
  93. }
  94. return state;
  95. }
  96. const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
  97. const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
  98. const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
  99. const actionPromise = (name, promise) =>
  100. async dispatch => {
  101. dispatch(actionPending(name))
  102. try {
  103. let data = await promise
  104. dispatch(actionResolved(name, data))
  105. return data
  106. }
  107. catch (error) {
  108. dispatch(actionRejected(name, error))
  109. }
  110. }
  111. const getGQL = url =>
  112. async (query, variables = {}) => {
  113. let obj = await fetch(url, {
  114. method: 'POST',
  115. headers: {
  116. "Content-Type": "application/json",
  117. Authorization: localStorage.authToken ? 'Bearer ' + localStorage.authToken : {},
  118. },
  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. const actionRootCats = () =>
  129. actionPromise('rootCats', gql(`query {
  130. CategoryFind(query: "[{\\"parent\\":null}]"){
  131. _id name
  132. }
  133. }`))
  134. const actionCatById = (_id) =>
  135. actionPromise('catById', gql(`query catById($q: String){
  136. CategoryFindOne(query: $q){
  137. subCategories{name, _id}
  138. _id name goods {
  139. _id name price images {
  140. url
  141. }
  142. }
  143. }
  144. }`, { q: JSON.stringify([{ _id }]) }))
  145. const store = createStore(combineReducers({promise: promiseReducer,
  146. auth: authReducer,
  147. cart: cartReducer}),
  148. applyMiddleware(thunk))
  149. store.subscribe(()=>console.log(store.getState()))
  150. store.dispatch(actionRootCats())
  151. store.dispatch(actionCatById('5dc49f4d5df9d670df48cc64'))
  152. const Logo = ({logo=logoDefault}) =>
  153. <a href='#' className='Logo'>
  154. <img src={logo} />
  155. </a>
  156. const KoshikGood = ({obj: {good: {_id, name, price, images}={}, count=1}}) => {
  157. console.log(name);
  158. console.log(count)
  159. return(
  160. <div className='GoodCard2'>
  161. <p>{name}</p>
  162. {images && images[0] && images[0].url && <img src={backURL+'/'+images[0].url} />}
  163. <p><input type='number' value={count} /></p>
  164. <p><strong>{price}</strong></p>
  165. </div>
  166. )
  167. }
  168. const KoshikGoods = ({goods}) =>{
  169. console.log(goods);
  170. return(
  171. <div className='GoodCard'>
  172. <h1>Корзина</h1>
  173. {Object.entries(goods).map(good2 =>{
  174. console.log(good2);
  175. return(
  176. <KoshikGood obj={good2[1]}/>
  177. )
  178. })}
  179. </div>
  180. )
  181. }
  182. const CKoshikGoods = connect(state => ({goods: state.cart}))(KoshikGoods)
  183. const Koshik = ({cart}) =>{
  184. let count = 0;
  185. let sum = Object.entries(cart).map(([, val]) => val.count);
  186. count = sum.reduce((a, b) => a + b, 0);
  187. return(
  188. <div>
  189. <div className='Koshik'>{count}</div>
  190. </div>
  191. )
  192. }
  193. const CKoshik = connect(({cart}) => ({cart}))(Koshik)
  194. const Header = ({logo=logoDefault}) =>
  195. <header>
  196. <Logo logo={logo} />
  197. <CKoshik />
  198. <CKoshikGoods />
  199. </header>
  200. const Footer = ({logo=logoDefault}) =>
  201. <footer>
  202. <Logo logo={logo} /> />
  203. </footer>
  204. const defaultRootCats = [
  205. {
  206. "_id": "5dc49f4d5df9d670df48cc64",
  207. "name": "Airconditions"
  208. },
  209. {
  210. "_id": "5dc458985df9d670df48cc47",
  211. "name": " Smartphones"
  212. },
  213. {
  214. "_id": "5dc4b2553f23b553bf354101",
  215. "name": "Крупная бытовая техника"
  216. },
  217. {
  218. "_id": "5dcac1b56d09c45440d14cf8",
  219. "name": "Макароны"
  220. }
  221. ]
  222. const RootCategory = ({cat:{_id,name}={}}) =>
  223. <li>
  224. <a href={`#/${_id}`}>{name}</a>
  225. </li>
  226. const RootCategories = ({cats=defaultRootCats}) =>
  227. <ul>
  228. {cats.map(cat=> <RootCategory cat={cat} />)}
  229. </ul>
  230. const CRootCategories = connect(state=>({cats: state.promise.rootCats?.payload || []}))(RootCategories)
  231. const Aside =()=>
  232. <aside>
  233. <CRootCategories />
  234. </aside>
  235. const Content =({children})=>
  236. <div className='Content'>
  237. {children}
  238. </div>
  239. const defaultCat = {
  240. "subCategories": null,
  241. "_id": "5dc458985df9d670df48cc47",
  242. "name": " Smartphones",
  243. "goods": [
  244. {
  245. "_id": "5dc4a3e15df9d670df48cc6b",
  246. "name": "Apple iPhone 11 Pro Max 64GB Gold",
  247. "price": 1500,
  248. "images": [
  249. {
  250. "url": "images/b599634ebfecf2a19d900e22434bedbd"
  251. }
  252. ]
  253. },
  254. {
  255. "_id": "5dc4a4365df9d670df48cc6c",
  256. "name": "Apple iPhone XS Max 256GB Gold",
  257. "price": 1300,
  258. "images": [
  259. {
  260. "url": "images/63c4a052377862494e33746b375903f6"
  261. }
  262. ]
  263. },
  264. {
  265. "_id": "61b1056cc750c12ba6ba4522",
  266. "name": "iPhone ",
  267. "price": 1000,
  268. "images": [
  269. {
  270. "url": "images/cc23c15a3ae1ac60582785ebf9b3d207"
  271. }
  272. ]
  273. },
  274. {
  275. "_id": "61b105f9c750c12ba6ba4524",
  276. "name": "iPhone ",
  277. "price": 1200,
  278. "images": [
  279. {
  280. "url": "images/50842a3af34bfa28be037aa644910d07"
  281. }
  282. ]
  283. },
  284. {
  285. "_id": "61b1069ac750c12ba6ba4526",
  286. "name": "iPhone ",
  287. "price": 1000,
  288. "images": [
  289. {
  290. "url": "images/d12b07d983dac81ccad404582a54d8be"
  291. }
  292. ]
  293. },
  294. {
  295. "_id": "61b23f94c750c12ba6ba472a",
  296. "name": "name1",
  297. "price": 1214,
  298. "images": [
  299. {
  300. "url": null
  301. }
  302. ]
  303. },
  304. {
  305. "_id": "61b23fbac750c12ba6ba472c",
  306. "name": "smart",
  307. "price": 1222,
  308. "images": [
  309. {
  310. "url": "images/871f4e6edbf86c35f70b72dcdebcd8b2"
  311. }
  312. ]
  313. }
  314. ]
  315. }
  316. const SubCategories = ({cats}) =>
  317. <></>
  318. const GoodCard = ({good: {_id,name,price,images}={}, onCartAdd}) =>
  319. <div className='GoodCard'>
  320. <h2>{name}</h2>
  321. {images && images[0] && images[0].url && <img src={backURL+'/'+images[0].url} />}
  322. <strong>{price}</strong>
  323. <button onClick={()=>onCartAdd({_id,name,price,images})}>+</button>
  324. </div>
  325. const CGoodCard = connect(null, {onCartAdd: actionCartAdd})(GoodCard)
  326. const Category = ({cat:{_id,name,goods,price,subCategories}=defaultCat}) =>
  327. <div className="Category">
  328. <h1>{name}</h1>
  329. {subCategories && <SubCategories cats={subCategories} />}
  330. {goods.map(good=> <CGoodCard good={good} />)}
  331. </div>
  332. const CCategory = connect(state=>({cat: state.promise.catById?.payload}))(Category)
  333. const Main = ()=>
  334. <main>
  335. <Aside />
  336. <Content>
  337. <CCategory />
  338. </Content>
  339. </main>
  340. const JSONTest = ({data}) =>
  341. <pre>
  342. {JSON.stringify(data,null,4)}
  343. </pre>
  344. const ReduxJSON = connect(state => ({data:state.promise}))(JSONTest)
  345. const ListItem = ({item}) =>
  346. <li>{item}</li>
  347. const List = ({data=["пиво","чипсы","сиги"]}) =>
  348. <ul>
  349. {data.map(item=><ListItem item={item} />)}
  350. </ul>
  351. function App() {
  352. return (
  353. <Provider store={store}>
  354. <div className="App">
  355. <Header />
  356. <Main />
  357. <Footer />
  358. </div>
  359. </Provider>
  360. );
  361. }
  362. export default App;