App.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. import logo from "./logo.svg";
  2. import React, { useState } from "react";
  3. import "./App.css";
  4. import thunk from "redux-thunk";
  5. import { createStore, combineReducers, applyMiddleware } from "redux";
  6. import { Provider, connect } from "react-redux";
  7. const store = createStore(
  8. combineReducers({
  9. auth: authReducer,
  10. promise: promiseReducer,
  11. cart: localStoreReducer(cartReducer, "cart"),
  12. }),
  13. applyMiddleware(thunk)
  14. );
  15. function jwtDecode(token) {
  16. try {
  17. return JSON.parse(atob(token.split(".")[1]));
  18. } catch (e) {}
  19. }
  20. const getGQL = (url) => (query, variables) =>
  21. fetch(url, {
  22. method: "POST",
  23. headers: {
  24. "Content-Type": "application/json",
  25. // 'Accept' : 'application/json',
  26. ...(localStorage.authToken
  27. ? { Authorization: "Bearer " + localStorage.authToken }
  28. : {}),
  29. },
  30. body: JSON.stringify({ query, variables }),
  31. })
  32. .then((res) => res.json())
  33. .then((data) => {
  34. if (data.data) {
  35. return Object.values(data.data)[0];
  36. } else throw new Error(JSON.stringify(data.errors));
  37. });
  38. const backendURL = "http://shop-roles.node.ed.asmer.org.ua";
  39. const gql = getGQL(backendURL + "/graphql");
  40. function authReducer(state, { type, token }) {
  41. if (state === undefined) {
  42. if (localStorage.authToken) {
  43. type = "AUTH_LOGIN";
  44. token = localStorage.authToken;
  45. }
  46. }
  47. if (type === "AUTH_LOGIN") {
  48. let payload = jwtDecode(token);
  49. if (payload) {
  50. localStorage.authToken = token;
  51. return { token, payload };
  52. }
  53. }
  54. if (type === "AUTH_LOGOUT") {
  55. localStorage.removeItem("authToken");
  56. return {};
  57. }
  58. return state || {};
  59. }
  60. const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
  61. const actionAuthLogout = () => (dispatch) => {
  62. dispatch({ type: "AUTH_LOGOUT" });
  63. localStorage.removeItem("authToken");
  64. };
  65. function promiseReducer(state = {}, { type, name, status, payload, error }) {
  66. if (type === "PROMISE") {
  67. return {
  68. ...state,
  69. [name]: { status, payload, error },
  70. };
  71. }
  72. return state;
  73. }
  74. const actionPending = (name) => ({
  75. type: "PROMISE",
  76. status: "PENDING",
  77. name,
  78. });
  79. const actionFulfilled = (name, payload) => ({
  80. type: "PROMISE",
  81. status: "FULFILLED",
  82. name,
  83. payload,
  84. });
  85. const actionRejected = (name, error) => ({
  86. type: "PROMISE",
  87. status: "REJECTED",
  88. name,
  89. error,
  90. });
  91. const actionPromise = (name, promise) => async (dispatch) => {
  92. try {
  93. dispatch(actionPending(name));
  94. let payload = await promise;
  95. dispatch(actionFulfilled(name, payload));
  96. return payload;
  97. } catch (e) {
  98. dispatch(actionRejected(name, e));
  99. }
  100. };
  101. function cartReducer(state = {}, { type, count = 1, good }) {
  102. // type CART_ADD CART_REMOVE CART_CLEAR CART_DEL
  103. // {
  104. // id1: {count: 1, good: {name, price, images, id}}
  105. // }
  106. if (type === "CART_ADD") {
  107. return {
  108. ...state,
  109. [good._id]: { count: count + (state[good._id]?.count || 0), good },
  110. };
  111. }
  112. if (type === "CART_DELETE") {
  113. if (state[good._id].count > 1) {
  114. return {
  115. ...state,
  116. [good._id]: {
  117. count: -count + (state[good._id]?.count || 0),
  118. good,
  119. },
  120. };
  121. }
  122. if (state[good._id].count === 1) {
  123. let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
  124. //delete newState[good._id]
  125. return newState;
  126. }
  127. }
  128. if (type === "CART_CLEAR") {
  129. return {};
  130. }
  131. if (type === "CART_REMOVE") {
  132. // let newState = {...state}
  133. let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
  134. //delete newState[good._id]
  135. return newState;
  136. }
  137. return state;
  138. }
  139. const actionCartAdd = (good, count = 1) => ({ type: "CART_ADD", good, count });
  140. const actionCartDelete = (good) => ({ type: "CART_DELETE", good });
  141. const actionCartClear = () => ({ type: "CART_CLEAR" });
  142. const actionCartRemove = (good) => ({ type: "CART_REMOVE", good });
  143. function localStoreReducer(reducer, localStorageKey) {
  144. function localStoredReducer(state, action) {
  145. // Если state === undefined, то достать старый state из local storage
  146. if (state === undefined) {
  147. try {
  148. return JSON.parse(localStorage[localStorageKey]);
  149. } catch (e) {}
  150. }
  151. const newState = reducer(state, action);
  152. // Сохранить newState в local storage
  153. localStorage[localStorageKey] = JSON.stringify(newState);
  154. return newState;
  155. }
  156. return localStoredReducer;
  157. }
  158. const actionRootCats = () =>
  159. actionPromise(
  160. "rootCats",
  161. gql(
  162. `query {
  163. CategoryFind(query: "[{\\"parent\\":null}]"){
  164. _id name
  165. }
  166. }`
  167. )
  168. );
  169. const actionCatById = (_id) =>
  170. actionPromise(
  171. "catById",
  172. gql(
  173. `query catById($q: String){
  174. CategoryFindOne(query: $q){
  175. _id name subCategories {
  176. name _id
  177. }
  178. goods {
  179. _id name price images {
  180. url
  181. }
  182. }
  183. }
  184. }`,
  185. { q: JSON.stringify([{ _id }]) }
  186. )
  187. );
  188. const actionLogin = (login, password) =>
  189. actionPromise(
  190. "actionLogin",
  191. gql(
  192. `query log($login:String, $password:String){
  193. login(login:$login, password:$password)
  194. }`,
  195. { login, password }
  196. )
  197. );
  198. const actionGoodById = (_id) =>
  199. actionPromise(
  200. "GoodFineOne",
  201. gql(
  202. `query goodByid($goodId: String) {
  203. GoodFindOne(query: $goodId) {
  204. _id
  205. name
  206. price
  207. description
  208. images {
  209. url
  210. }
  211. }
  212. }`,
  213. { goodId: JSON.stringify([{ _id }]) }
  214. )
  215. );
  216. store.dispatch(actionRootCats());
  217. store.dispatch(actionCatById("6262ca7dbf8b206433f5b3d1"));
  218. const actionFullLogin = (log, pass) => async (dispatch) => {
  219. let token = await dispatch(
  220. actionPromise(
  221. "login",
  222. gql(
  223. `query login($login: String, $password: String) {
  224. login(login: $login, password: $password)
  225. }`,
  226. { login: log, password: pass }
  227. )
  228. )
  229. );
  230. if (token) {
  231. dispatch(actionAuthLogin(token));
  232. }
  233. };
  234. const actionFullRegister = (login, password) => async (dispatch) => {
  235. let user = await dispatch(
  236. actionPromise(
  237. "register",
  238. gql(
  239. `mutation register($login: String, $password: String) {
  240. UserUpsert(user: {login: $login, password: $password}) {
  241. _id
  242. login
  243. }
  244. }`,
  245. { login: login, password: password }
  246. )
  247. )
  248. );
  249. if (user) {
  250. dispatch(actionFullLogin(login, password));
  251. }
  252. };
  253. const actionOrder = () => async (dispatch, getState) => {
  254. let { cart } = getState();
  255. const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({
  256. good: { _id },
  257. count,
  258. }));
  259. let result = await dispatch(
  260. actionPromise(
  261. "order",
  262. gql(
  263. `
  264. mutation newOrder($order:OrderInput){
  265. OrderUpsert(order:$order)
  266. { _id total }
  267. }
  268. `,
  269. { order: { orderGoods } }
  270. )
  271. )
  272. );
  273. if (result?._id) {
  274. dispatch(actionCartClear());
  275. document.location.hash = "#/cart/";
  276. alert("Purchase completed");
  277. }
  278. };
  279. const orderHistory = () =>
  280. actionPromise(
  281. "history",
  282. gql(` query OrderFind{
  283. OrderFind(query:"[{}]"){
  284. _id total createdAt orderGoods{
  285. count good{
  286. _id name price images{
  287. url
  288. }
  289. }
  290. owner{
  291. _id login
  292. }
  293. }
  294. }
  295. }
  296. `)
  297. );
  298. const LoginForm = ({ onLogin }) => {
  299. const [login, setLogin] = useState("");
  300. const [password, setPassword] = useState("");
  301. const checkDisable = () => {
  302. return !(login !== "" && password.match(/(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/));
  303. };
  304. const Authorization = () => {
  305. onLogin(login, password);
  306. };
  307. return (
  308. <div className="formBlock">
  309. <p>Enter login:</p>
  310. <input
  311. type="text"
  312. value={login}
  313. onChange={(e) => setLogin(e.target.value)}
  314. />
  315. <p>Enter password:</p>
  316. <input
  317. type="password"
  318. value={password}
  319. onChange={(e) => setPassword(e.target.value)}
  320. />
  321. <button disabled={checkDisable()} onClick={Authorization}>
  322. Log In
  323. </button>
  324. </div>
  325. );
  326. };
  327. if (localStorage.authToken) {
  328. store.dispatch(actionAuthLogin(localStorage.authToken));
  329. }
  330. store.subscribe(() => console.log(store.getState()));
  331. const cat = [
  332. {
  333. _id: "62d57ab8b74e1f5f2ec1a148",
  334. name: "Motorola Razr 5G 8/256GB Graphite",
  335. price: 3500,
  336. },
  337. {
  338. _id: "62d57c4db74e1f5f2ec1a14a",
  339. name: "Смартфон Google Pixel 6 Pro 12/128GB Stormy Black",
  340. price: 2800,
  341. },
  342. {
  343. _id: "62d58318b74e1f5f2ec1a14e",
  344. name: "Microsoft Surface Duo 2 8GB/256GB",
  345. price: 4500,
  346. },
  347. {
  348. _id: "62d5869bb74e1f5f2ec1a150",
  349. name: "Смартфон Poco F3 6/128GB EU Arctic White",
  350. price: 1800,
  351. },
  352. {
  353. _id: "62d58810b74e1f5f2ec1a152",
  354. name: "Мобильный телефон Xiaomi Redmi Note 9 4G (Redmi 9t EU)",
  355. price: 800,
  356. },
  357. {
  358. _id: "62d5a7deb74e1f5f2ec1a154",
  359. name: "LG V50 black REF",
  360. price: 900,
  361. },
  362. ];
  363. const rootCats = [
  364. {
  365. name: "test3",
  366. },
  367. {
  368. name: "Tools",
  369. },
  370. {
  371. name: "Tomatoes",
  372. },
  373. {
  374. name: "123",
  375. },
  376. {
  377. name: "iPhone",
  378. },
  379. {
  380. name: "Samsung",
  381. },
  382. {
  383. name: "Smartphone",
  384. },
  385. {
  386. name: "Large home appliances",
  387. },
  388. {
  389. name: "Garden",
  390. },
  391. {
  392. name: "Children's products",
  393. },
  394. {
  395. name: " Hobbies and sports",
  396. },
  397. {
  398. name: "Sale",
  399. },
  400. ];
  401. const CategoryMenu = ({rootCats = []}) => {
  402. return (
  403. <>
  404. <aside id="aside">
  405. <ul>
  406. {rootCats.map((cat) => (
  407. <CategoryMenuItem name={cat.name} />
  408. ))}
  409. </ul>
  410. </aside>
  411. </>
  412. );
  413. };
  414. const CCategoryMenu = connect((state) => ({rootCats: state.promise?.rootCats.payload}))(CategoryMenu)
  415. const CategoryMenuItem = ({ name }) => {
  416. return (
  417. <>
  418. <h2>{name}</h2>
  419. </>
  420. );
  421. };
  422. const Category = ({cat = []}) => {
  423. return (
  424. <div className="cardBlock">
  425. {cat.map((item) => (
  426. <GoodCard name={item.name} price={item.price} />
  427. ))}
  428. </div>
  429. );
  430. };
  431. const CCategory = connect((state) => ({cat: state.promise?.catById.payload?.goods}))(Category)
  432. const GoodCard = ({ name, price }) => {
  433. return (
  434. <div className="card">
  435. <h3>{name}</h3>
  436. <h4>{price}</h4>
  437. </div>
  438. );
  439. };
  440. const CLoginForm = connect(null, { onLogin: actionFullLogin })(LoginForm);
  441. function App() {
  442. return (
  443. <Provider store={store}>
  444. <div className="App">
  445. <CLoginForm />
  446. <CCategoryMenu />
  447. <CCategory />
  448. </div>
  449. </Provider>
  450. );
  451. }
  452. export default App;