App.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. import { useState } from 'react';
  2. import './App.css';
  3. import thunk from 'redux-thunk';
  4. import {createStore, combineReducers, applyMiddleware} from 'redux';
  5. import { Provider, connect } from 'react-redux';
  6. const store = createStore(combineReducers({
  7. auth: authReducer,
  8. promise: promiseReducer,
  9. cart: localStoreReducer(cartReducer, "cart")
  10. }), applyMiddleware(thunk))
  11. function jwtDecode(token) {
  12. try {
  13. return JSON.parse(atob(token.split('.')[1]))
  14. }
  15. catch (e) {
  16. }
  17. }
  18. function authReducer(state = {}, { type, token }) {
  19. //{
  20. // token, payload
  21. //}
  22. if (type === 'AUTH_LOGIN') {
  23. //пытаемся токен раскодировать
  24. const payload = jwtDecode(token)
  25. if (payload) { //и если получилось
  26. return {
  27. token, payload //payload - раскодированный токен;
  28. }
  29. }
  30. }
  31. if (type === 'AUTH_LOGOUT') {
  32. return {}
  33. }
  34. return state;
  35. }
  36. function countReducer(state = { count: 0 }, { type }) {
  37. if (type === "COUNT_INC") {
  38. return {
  39. count: state.count + 1
  40. }
  41. }
  42. if (type === "COUNT_DEC") {
  43. return {
  44. count: state.count - 1
  45. }
  46. }
  47. return state
  48. }
  49. function localStoreReducer(reducer, localStorageKey) {
  50. function localStoredReducer(state, action) {
  51. // Если state === undefined, то достать старый state из local storage
  52. if (state === undefined) {
  53. try {
  54. return JSON.parse(localStorage[localStorageKey])
  55. } catch (e) { }
  56. }
  57. const newState = reducer(state, action)
  58. // Сохранить newState в local storage
  59. localStorage[localStorageKey] = JSON.stringify(newState)
  60. return newState
  61. }
  62. return localStoredReducer
  63. }
  64. function promiseReducer(state = {}, { type, name, status, payload, error }) {
  65. ////?????
  66. //ОДИН ПРОМИС:
  67. //состояние: PENDING/FULFILLED/REJECTED
  68. //результат
  69. //ошибка:
  70. //{status, payload, error}
  71. //{
  72. // name1:{status, payload, error}
  73. // name2:{status, payload, error}
  74. // name3:{status, payload, error}
  75. //}
  76. if (type === 'PROMISE') {
  77. return {
  78. ...state,
  79. [name]: { status, payload, error }
  80. }
  81. }
  82. return state
  83. }
  84. const actionPending = (name) => ({
  85. type: 'PROMISE',
  86. status: 'PENDING',
  87. name
  88. })
  89. const actionFulfilled = (name, payload) => ({
  90. type: 'PROMISE',
  91. status: 'FULFILLED',
  92. name,
  93. payload
  94. })
  95. const actionRejected = (name, error) => ({
  96. type: 'PROMISE',
  97. status: 'REJECTED',
  98. name,
  99. error
  100. })
  101. const actionPromise = (name, promise) =>
  102. async dispatch => {
  103. try {
  104. dispatch(actionPending(name))
  105. let payload = await promise
  106. dispatch(actionFulfilled(name, payload))
  107. return payload
  108. }
  109. catch (e) {
  110. dispatch(actionRejected(name, e))
  111. }
  112. }
  113. // const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  114. // function combineReducers(reducers) { //пачку редьюсеров как объект {auth: authReducer, promise: promiseReducer}
  115. // function combinedReducer(combinedState = {}, action) { //combinedState - типа {auth: {...}, promise: {....}}
  116. // const newCombinedState = {}
  117. // for (const [reducerName, reducer] of Object.entries(reducers)) {
  118. // const newSubState = reducer(combinedState[reducerName], action)
  119. // if (newSubState !== combinedState[reducerName]) {
  120. // newCombinedState[reducerName] = newSubState
  121. // }
  122. // }
  123. // if (Object.keys(newCombinedState).length === 0) {
  124. // return combinedState
  125. // }
  126. // return { ...combinedState, ...newCombinedState }
  127. // }
  128. // return combinedReducer //нам возвращают один редьюсер, который имеет стейт вида {auth: {...стейт authReducer-а}, promise: {...стейт promiseReducer-а}}
  129. // }
  130. function cartReducer(state = {}, { type, count = 1, good }) {
  131. // type CART_ADD CART_REMOVE CART_CLEAR CART_DEL
  132. // {
  133. // id1: {count: 1, good: {name, price, images, id}}
  134. // }
  135. if (type === "CART_ADD") {
  136. return {
  137. ...state,
  138. [good._id]: { count: count + (state[good._id]?.count || 0), good },
  139. };
  140. }
  141. if (type === "CART_DELETE") {
  142. if (state[good._id].count > 1) {
  143. return {
  144. ...state,
  145. [good._id]: {
  146. count: -count + (state[good._id]?.count || 0),
  147. good,
  148. },
  149. };
  150. }
  151. if (state[good._id].count === 1) {
  152. let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
  153. //delete newState[good._id]
  154. return newState;
  155. }
  156. }
  157. if (type === "CART_CLEAR") {
  158. return {};
  159. }
  160. if (type === "CART_REMOVE") {
  161. // let newState = {...state}
  162. let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
  163. //delete newState[good._id]
  164. return newState;
  165. }
  166. return state;
  167. }
  168. const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/'
  169. const getGQL = (url) => (query, variables) =>
  170. fetch(url, {
  171. method: "POST",
  172. headers: {
  173. "Content-Type": "application/json",
  174. ...(localStorage.authToken
  175. ? { Authorization: "Bearer " + localStorage.authToken }
  176. : {}),
  177. },
  178. body: JSON.stringify({ query, variables }),
  179. })
  180. .then((res) => res.json())
  181. .then((data) => {
  182. if (data.data) {
  183. return Object.values(data.data)[0];
  184. } else throw new Error(JSON.stringify(data.errors));
  185. });
  186. const gql = getGQL(backendURL + "graphql");
  187. // const gql = (url, query, variables) => fetch(url, {
  188. // method: 'POST',
  189. // headers: {
  190. // "Content-Type": "application/json",
  191. // Accept: "application/json",
  192. // },
  193. // body: JSON.stringify({ query, variables })
  194. // }).then(res => res.json())
  195. // const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/graphql'
  196. const actionRootCats = () =>
  197. actionPromise(
  198. 'rootCats',
  199. gql(
  200. `query {
  201. CategoryFind(query: "[{\\"parent\\":null}]"){
  202. _id name
  203. }
  204. }`
  205. )
  206. )
  207. const actionCatById = (_id) => //добавить подкатегории
  208. actionPromise(
  209. 'catById',
  210. gql(
  211. `query catById($q: String){
  212. CategoryFindOne(query: $q){
  213. _id name goods {
  214. _id name price images {
  215. url
  216. }
  217. }
  218. }
  219. }`,
  220. { q: JSON.stringify([{ _id }]) }
  221. )
  222. )
  223. const actionGoodById = (_id) =>
  224. actionPromise(
  225. 'goodByID',
  226. gql(
  227. `query goodByID($q:String){
  228. GoodFindOne(query: $q){
  229. _id
  230. name
  231. description
  232. price
  233. categories{
  234. _id
  235. name
  236. }
  237. images{
  238. url
  239. }
  240. }
  241. }`,
  242. { q: JSON.stringify([{ _id }]) }
  243. )
  244. )
  245. const actionRegistr = (login, password) =>
  246. actionPromise(
  247. 'registr',
  248. gql(
  249. `mutation register($login:String, $password:String){
  250. UserUpsert(user: {login:$login, password:$password}){
  251. _id login
  252. }
  253. }`,
  254. { login: login, password: password }
  255. )
  256. )
  257. const actionLogin = (login, password) =>
  258. actionPromise(
  259. 'login',
  260. gql(
  261. `query log($login:String, $password:String){
  262. login(login:$login, password:$password)
  263. }`,
  264. { login: login, password: password }
  265. )
  266. )
  267. const actionOrder = () => async (dispatch, getState) => {
  268. let { cart } = getState();
  269. const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({
  270. good: { _id },
  271. count,
  272. }));
  273. let result = await dispatch(
  274. actionPromise(
  275. "order",
  276. gql(
  277. `
  278. mutation newOrder($order:OrderInput){
  279. OrderUpsert(order:$order)
  280. { _id total }
  281. }
  282. `,
  283. { order: { orderGoods } }
  284. )
  285. )
  286. );
  287. if (result?._id) {
  288. dispatch(actionCartClear());
  289. document.location.hash = "#/cart/";
  290. alert("Покупка успішна")
  291. }
  292. };
  293. const orderHistory = () =>
  294. actionPromise(
  295. "history",
  296. gql(` query OrderFind{
  297. OrderFind(query:"[{}]"){
  298. _id total createdAt orderGoods{
  299. count good{
  300. _id name price images{
  301. url
  302. }
  303. }
  304. owner{
  305. _id login
  306. }
  307. }
  308. }
  309. }
  310. `)
  311. );
  312. const actionAuthLogin = (token) =>
  313. (dispatch, getState) => {
  314. const oldState = getState()
  315. dispatch({ type: 'AUTH_LOGIN', token })
  316. const newState = getState()
  317. if (oldState !== newState)
  318. localStorage.authToken = token
  319. }
  320. const actionAuthLogout = () =>
  321. dispatch => {
  322. dispatch({ type: 'AUTH_LOGOUT' })
  323. localStorage.removeItem('authToken')
  324. }
  325. const actionCartAdd = (good, count = 1) => ({
  326. type: "CART_ADD",
  327. good,
  328. count
  329. });
  330. const actionCartChange = (good, count = 1) => ({
  331. type: "CART_CHANGE",
  332. good,
  333. count,
  334. }); ///oninput меняяем полностью
  335. const actionCartDelete = (good) => ({
  336. type: "CART_DELETE",
  337. good
  338. });
  339. const actionCartClear = () => ({
  340. type: "CART_CLEAR"
  341. });
  342. const actionCartRemove = (good) =>({
  343. type: "CART_REMOVE",
  344. good
  345. })
  346. const actionFullLogin = (login, password) => async (dispatch) => {
  347. let token = await dispatch(actionLogin(login, password))
  348. if (token) {
  349. dispatch(actionAuthLogin(token))
  350. }
  351. }
  352. const actionFullRegistr = (login, password) => async (dispatch) => {
  353. try {
  354. await dispatch(actionRegistr(login, password))
  355. }
  356. catch (e) {
  357. return console.log(e)
  358. }
  359. await dispatch(actionFullLogin(login, password))
  360. }
  361. //не забудьте combineReducers если он у вас уже есть
  362. if (localStorage.authToken) {
  363. store.dispatch(actionAuthLogin(localStorage.authToken))
  364. }
  365. //const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}))
  366. store.subscribe(() => console.log(store.getState()))
  367. store.dispatch(actionRootCats())
  368. // store.dispatch(actionLogin('test456', '123123'))
  369. const LoginForm = ({onLogin}) => {
  370. const [login, setLogin] = useState('')
  371. const [password, setPassword] = useState('')
  372. const disableButton = () => {
  373. return !(login !== '' && password !== '')
  374. }
  375. const Vhod = () => {
  376. onLogin(login,password)
  377. }
  378. return(
  379. <div className = 'loginForm'>
  380. <strong>Введите ваш логин : </strong>
  381. <input type = 'text'
  382. value = {login}
  383. onChange = { (e) => setLogin(e.target.value)}
  384. />
  385. <strong> Введите ваш пароль : </strong>
  386. <input type = 'password'
  387. value = {password}
  388. onChange = { (e) => setPassword(e.target.value) }
  389. />
  390. <button disabled = {disableButton()} onClick = {Vhod}
  391. >Вход</button>
  392. </div>
  393. )
  394. }
  395. const rootCats =[
  396. {
  397. 'name': 'Tools'
  398. },
  399. {
  400. 'name': 'Tomatoes'
  401. },
  402. {
  403. 'name': 'Smartphone'
  404. },
  405. {
  406. 'name': 'Garden'
  407. },
  408. {
  409. 'name': 'Childrens products'
  410. },
  411. {
  412. 'name': 'Hobbies and sports'
  413. },
  414. {
  415. 'name': 'Sale'
  416. }
  417. ]
  418. const cats = [
  419. {'_id' : '62d403fbb74e1f5f2ec1a12a',
  420. 'name' : 'Виски Glengoyne 50yo 0,725 л',
  421. 'price': 1250,
  422. },
  423. {'_id' : '62d40566b74e1f5f2ec1a12c',
  424. 'name' : 'LONGINES HYDROCONQUEST L3.781.4.56.9',
  425. 'price': 5600,
  426. },
  427. {'_id' : '62d40618b74e1f5f2ec1a12e',
  428. 'name' : 'Лобзик электрический JS08-100A',
  429. 'price': 780,
  430. },
  431. ]
  432. const CategoryMenuItem = ({name}) =>{
  433. return(
  434. <div className ="menuItem">
  435. <strong>{name}</strong>
  436. </div>
  437. )}
  438. const CategoryMenu = () =>
  439. <aside className ='aside'>
  440. <ul>
  441. {rootCats.map(cats => <CategoryMenuItem name = {cats.name} />)}
  442. </ul>
  443. </aside>
  444. const GoodCard = ({catsis:{_id, name, price}}) =>
  445. <div className ="divCats">
  446. <li className ="kartochki">
  447. <p>Id: {_id}</p>
  448. <h1>{name}</h1>
  449. <strong>Price: {price}</strong>
  450. </li>
  451. </div>
  452. const Category = () =>
  453. <main className = 'main'>
  454. <ul>
  455. {cats.map(catsis=> <GoodCard catsis ={catsis} />)}
  456. </ul>
  457. </main>
  458. const CLoginForm = connect(null, {onLogin: actionFullLogin})(LoginForm)
  459. function App() {
  460. return (
  461. <Provider store = {store}>
  462. <div className="App">
  463. <CLoginForm />
  464. <div className ='shop'>
  465. <CategoryMenu />
  466. <Category />
  467. </div>
  468. </div>
  469. </Provider>
  470. );
  471. }
  472. export default App;