Jelajahi Sumber

create Login/Registrations forms

Alex 2 tahun lalu
induk
melakukan
67a37acb7a

+ 263 - 6
package-lock.json

@@ -10,19 +10,26 @@
       "dependencies": {
         "@emotion/react": "^11.7.1",
         "@emotion/styled": "^11.6.0",
+        "@mui/base": "^5.0.0-alpha.63",
         "@mui/icons-material": "^5.2.5",
         "@mui/lab": "^5.0.0-alpha.62",
         "@mui/material": "^5.2.5",
         "@mui/styles": "^5.2.3",
+        "@mui/system": "^5.2.6",
         "@testing-library/jest-dom": "^5.16.1",
         "@testing-library/react": "^12.1.2",
         "@testing-library/user-event": "^13.5.0",
         "google-maps-react": "^2.0.6",
         "node-sass": "^7.0.0",
+        "prop-types": "^15.8.0",
         "react": "^17.0.2",
         "react-dom": "^17.0.2",
+        "react-redux": "^7.2.6",
         "react-router-dom": "^5.3.0",
         "react-scripts": "5.0.0",
+        "redux": "^4.1.2",
+        "redux-form": "^8.3.8",
+        "redux-thunk": "^2.4.1",
         "web-vitals": "^2.1.2"
       }
     },
@@ -2843,9 +2850,9 @@
       }
     },
     "node_modules/@mui/base": {
-      "version": "5.0.0-alpha.61",
-      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.61.tgz",
-      "integrity": "sha512-xB2EksQsLYOwmBQRDdcJjKAflgBYE9KBZS/TBxIKBlfi4w1v7kCi5VCHTHIQgPSjgoTSt3hyI+yHO2fLvCbUWQ==",
+      "version": "5.0.0-alpha.63",
+      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.63.tgz",
+      "integrity": "sha512-/jURXlBAYqZaWw92XfJgc6aiHnDcNVuRxBskub57HXWCMK3OiVVdfUEWJEdCjOacCKiw3+Etc5alI9Omaqrg2g==",
       "dependencies": {
         "@babel/runtime": "^7.16.3",
         "@emotion/is-prop-valid": "^1.1.1",
@@ -3027,6 +3034,37 @@
         }
       }
     },
+    "node_modules/@mui/material/node_modules/@mui/base": {
+      "version": "5.0.0-alpha.61",
+      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.61.tgz",
+      "integrity": "sha512-xB2EksQsLYOwmBQRDdcJjKAflgBYE9KBZS/TBxIKBlfi4w1v7kCi5VCHTHIQgPSjgoTSt3hyI+yHO2fLvCbUWQ==",
+      "dependencies": {
+        "@babel/runtime": "^7.16.3",
+        "@emotion/is-prop-valid": "^1.1.1",
+        "@mui/utils": "^5.2.3",
+        "@popperjs/core": "^2.4.4",
+        "clsx": "^1.1.1",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.8.6 || ^17.0.0",
+        "react": "^17.0.2",
+        "react-dom": "^17.0.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@mui/private-theming": {
       "version": "5.2.3",
       "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.2.3.tgz",
@@ -3963,6 +4001,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "dependencies": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "node_modules/@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -4084,6 +4131,17 @@
         "@types/react": "*"
       }
     },
+    "node_modules/@types/react-redux": {
+      "version": "7.1.21",
+      "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.21.tgz",
+      "integrity": "sha512-bLdglUiBSQNzWVVbmNPKGYYjrzp3/YDPwfOH3nLEz99I4awLlaRAPWjo6bZ2POpxztFWtDDXIPxBLVykXqBt+w==",
+      "dependencies": {
+        "@types/hoist-non-react-statics": "^3.3.0",
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0",
+        "redux": "^4.0.0"
+      }
+    },
     "node_modules/@types/react-transition-group": {
       "version": "4.4.4",
       "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
@@ -7096,6 +7154,11 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/es6-error": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
+    },
     "node_modules/escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -9410,6 +9473,14 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/invariant": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+      "dependencies": {
+        "loose-envify": "^1.0.0"
+      }
+    },
     "node_modules/ip": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@@ -9648,6 +9719,11 @@
       "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
       "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
     },
+    "node_modules/is-promise": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+      "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+    },
     "node_modules/is-regex": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -14801,6 +14877,30 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "node_modules/react-redux": {
+      "version": "7.2.6",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
+      "integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
+      "dependencies": {
+        "@babel/runtime": "^7.15.4",
+        "@types/react-redux": "^7.1.20",
+        "hoist-non-react-statics": "^3.3.2",
+        "loose-envify": "^1.4.0",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2"
+      },
+      "peerDependencies": {
+        "react": "^16.8.3 || ^17"
+      },
+      "peerDependenciesMeta": {
+        "react-dom": {
+          "optional": true
+        },
+        "react-native": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -15116,6 +15216,60 @@
         "node": ">=8"
       }
     },
+    "node_modules/redux": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
+      "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
+      "dependencies": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "node_modules/redux-form": {
+      "version": "8.3.8",
+      "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.8.tgz",
+      "integrity": "sha512-PzXhA0d+awIc4PkuhbDa6dCEiraMrGMyyDlYEVNX6qEyW/G2SqZXrjav5zrpXb0CCeqQSc9iqwbMtYQXbJbOAQ==",
+      "dependencies": {
+        "@babel/runtime": "^7.9.2",
+        "es6-error": "^4.1.1",
+        "hoist-non-react-statics": "^3.3.2",
+        "invariant": "^2.2.4",
+        "is-promise": "^2.1.0",
+        "lodash": "^4.17.15",
+        "prop-types": "^15.6.1",
+        "react-is": "^16.4.2"
+      },
+      "engines": {
+        "node": ">=8.10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/redux-form"
+      },
+      "peerDependencies": {
+        "immutable": "^3.8.2 || ^4.0.0",
+        "react": "^16.4.2 || ^17.0.0",
+        "react-redux": "^6.0.1 || ^7.0.0",
+        "redux": "^3.7.2 || ^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "immutable": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/redux-form/node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
+    "node_modules/redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "peerDependencies": {
+        "redux": "^4"
+      }
+    },
     "node_modules/regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -20287,9 +20441,9 @@
       }
     },
     "@mui/base": {
-      "version": "5.0.0-alpha.61",
-      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.61.tgz",
-      "integrity": "sha512-xB2EksQsLYOwmBQRDdcJjKAflgBYE9KBZS/TBxIKBlfi4w1v7kCi5VCHTHIQgPSjgoTSt3hyI+yHO2fLvCbUWQ==",
+      "version": "5.0.0-alpha.63",
+      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.63.tgz",
+      "integrity": "sha512-/jURXlBAYqZaWw92XfJgc6aiHnDcNVuRxBskub57HXWCMK3OiVVdfUEWJEdCjOacCKiw3+Etc5alI9Omaqrg2g==",
       "requires": {
         "@babel/runtime": "^7.16.3",
         "@emotion/is-prop-valid": "^1.1.1",
@@ -20361,6 +20515,22 @@
         "prop-types": "^15.7.2",
         "react-is": "^17.0.2",
         "react-transition-group": "^4.4.2"
+      },
+      "dependencies": {
+        "@mui/base": {
+          "version": "5.0.0-alpha.61",
+          "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.61.tgz",
+          "integrity": "sha512-xB2EksQsLYOwmBQRDdcJjKAflgBYE9KBZS/TBxIKBlfi4w1v7kCi5VCHTHIQgPSjgoTSt3hyI+yHO2fLvCbUWQ==",
+          "requires": {
+            "@babel/runtime": "^7.16.3",
+            "@emotion/is-prop-valid": "^1.1.1",
+            "@mui/utils": "^5.2.3",
+            "@popperjs/core": "^2.4.4",
+            "clsx": "^1.1.1",
+            "prop-types": "^15.7.2",
+            "react-is": "^17.0.2"
+          }
+        }
       }
     },
     "@mui/private-theming": {
@@ -20966,6 +21136,15 @@
         "@types/node": "*"
       }
     },
+    "@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "requires": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -21087,6 +21266,17 @@
         "@types/react": "*"
       }
     },
+    "@types/react-redux": {
+      "version": "7.1.21",
+      "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.21.tgz",
+      "integrity": "sha512-bLdglUiBSQNzWVVbmNPKGYYjrzp3/YDPwfOH3nLEz99I4awLlaRAPWjo6bZ2POpxztFWtDDXIPxBLVykXqBt+w==",
+      "requires": {
+        "@types/hoist-non-react-statics": "^3.3.0",
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0",
+        "redux": "^4.0.0"
+      }
+    },
     "@types/react-transition-group": {
       "version": "4.4.4",
       "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
@@ -23363,6 +23553,11 @@
         "is-symbol": "^1.0.2"
       }
     },
+    "es6-error": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
+    },
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -25049,6 +25244,14 @@
         "side-channel": "^1.0.4"
       }
     },
+    "invariant": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+      "requires": {
+        "loose-envify": "^1.0.0"
+      }
+    },
     "ip": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@@ -25200,6 +25403,11 @@
       "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
       "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
     },
+    "is-promise": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+      "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+    },
     "is-regex": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -28832,6 +29040,19 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "react-redux": {
+      "version": "7.2.6",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
+      "integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==",
+      "requires": {
+        "@babel/runtime": "^7.15.4",
+        "@types/react-redux": "^7.1.20",
+        "hoist-non-react-statics": "^3.3.2",
+        "loose-envify": "^1.4.0",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2"
+      }
+    },
     "react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -29078,6 +29299,42 @@
         "strip-indent": "^3.0.0"
       }
     },
+    "redux": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz",
+      "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==",
+      "requires": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "redux-form": {
+      "version": "8.3.8",
+      "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-8.3.8.tgz",
+      "integrity": "sha512-PzXhA0d+awIc4PkuhbDa6dCEiraMrGMyyDlYEVNX6qEyW/G2SqZXrjav5zrpXb0CCeqQSc9iqwbMtYQXbJbOAQ==",
+      "requires": {
+        "@babel/runtime": "^7.9.2",
+        "es6-error": "^4.1.1",
+        "hoist-non-react-statics": "^3.3.2",
+        "invariant": "^2.2.4",
+        "is-promise": "^2.1.0",
+        "lodash": "^4.17.15",
+        "prop-types": "^15.6.1",
+        "react-is": "^16.4.2"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
+    "redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "requires": {}
+    },
     "regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",

+ 7 - 0
package.json

@@ -5,19 +5,26 @@
   "dependencies": {
     "@emotion/react": "^11.7.1",
     "@emotion/styled": "^11.6.0",
+    "@mui/base": "^5.0.0-alpha.63",
     "@mui/icons-material": "^5.2.5",
     "@mui/lab": "^5.0.0-alpha.62",
     "@mui/material": "^5.2.5",
     "@mui/styles": "^5.2.3",
+    "@mui/system": "^5.2.6",
     "@testing-library/jest-dom": "^5.16.1",
     "@testing-library/react": "^12.1.2",
     "@testing-library/user-event": "^13.5.0",
     "google-maps-react": "^2.0.6",
     "node-sass": "^7.0.0",
+    "prop-types": "^15.8.0",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
+    "react-redux": "^7.2.6",
     "react-router-dom": "^5.3.0",
     "react-scripts": "5.0.0",
+    "redux": "^4.1.2",
+    "redux-form": "^8.3.8",
+    "redux-thunk": "^2.4.1",
     "web-vitals": "^2.1.2"
   },
   "scripts": {

+ 18 - 11
src/App.js

@@ -1,5 +1,4 @@
-import './App.scss';
-import {Router, Route, Link, Redirect} from 'react-router-dom';
+import {Router, Route} from 'react-router-dom';
 import createHistory from "history/createBrowserHistory";
 import MainPage from "./pages/MainPage";
 import Page404 from "./pages/404Page";
@@ -9,21 +8,29 @@ import PrivacyPolicy from "./pages/PrivacyPolicyPage";
 import FAQPage from "./pages/FAQPage";
 import OurTeamPage from "./pages/OurTeamPage";
 import AboutUsPage from "./pages/AboutUsPage";
+import {Provider} from "react-redux";
+import MyAccountPage from "./pages/MyAccountPage";
+import {store} from "./reducers";
+import ProfilePage from "./pages/ProfilePage";
 
 const history = createHistory();
 
 function App() {
   return (
       <Router history={history}>
-          <Switch>
-              <Route path="/" component={MainPage} exact/>
-              <Route path="/about-us" component={AboutUsPage} />
-              <Route path="/our-team" component={OurTeamPage} />
-              <Route path="/faq" component={FAQPage} />
-              <Route path="/contact" component={ContactPage} />
-              <Route path="/privacy-policy" component={PrivacyPolicy} />
-              <Route path="*" component={Page404} />
-          </Switch>
+          <Provider store={store}>
+              <Switch>
+                  <Route path="/" component={MainPage} exact/>
+                  <Route path="/about-us" component={AboutUsPage} />
+                  <Route path="/our-team" component={OurTeamPage} />
+                  <Route path="/faq" component={FAQPage} />
+                  <Route path="/contact" component={ContactPage} />
+                  <Route path="/my-account" component={MyAccountPage} />
+                  <Route path="/privacy-policy" component={PrivacyPolicy} />
+                  <Route path="/profile" component={ProfilePage} />
+                  <Route path="*" component={Page404} />
+              </Switch>
+          </Provider>
       </Router>
   )
 }

+ 0 - 1
src/App.scss

@@ -1 +0,0 @@
-

+ 17 - 0
src/actions/ActionGQL.js

@@ -0,0 +1,17 @@
+const getGQL = url =>
+    async (query, variables = {}) => {
+        let obj = await fetch(url, {
+            method: 'POST',
+            headers: {
+                "Content-Type": "application/json",
+                Authorization: localStorage.authToken ? 'Bearer ' + localStorage.authToken : {},
+            },
+            body: JSON.stringify({ query, variables })
+        })
+        let a = await obj.json()
+        if (!a.data && a.errors)
+            throw new Error(JSON.stringify(a.errors))
+        return a.data[Object.keys(a.data)[0]]
+    }
+export const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
+export const gql = getGQL(backURL + '/graphql');

+ 44 - 0
src/actions/ActionLogin.js

@@ -0,0 +1,44 @@
+import {store} from "../reducers";
+
+const {actionAuthLogin} = require("../reducers/AuthReducer");
+const {actionPromise} = require("../reducers/PromiseReducer");
+const {gql} = require("./ActionGQL");
+
+
+const actionLogin = (login, password) => {
+    return actionPromise('login', gql(`query login($login: String, $password: String){
+      login(login: $login, password: $password)
+    }`, {login: login, password: password}))
+}
+
+export const actionFullLogin = (login, password) =>
+    async dispatch => {
+        let token = await dispatch(actionLogin(login, password))
+        if (token){
+            dispatch(actionAuthLogin(token))
+        }
+    }
+
+
+export const actionRegister = (login, password) => {
+    return actionPromise('register', gql(`mutation register($login:String, $password: String){
+      UserUpsert(user:{
+                 login: $login, 
+                 password: $password, 
+                 nick: $login}){
+        _id login
+      }
+    }`, {login: login, password: password}))
+}
+
+export const actionFullRegister = (login, password) =>
+    async dispatch => {
+        let allow = await dispatch(actionRegister(login, password))
+        if (allow) {
+            let token = await dispatch(actionLogin(login, password))
+            if (token) {
+                console.log('good')
+                dispatch(actionAuthLogin(token))
+            }
+        }
+    }

+ 71 - 36
src/components/Header.jsx

@@ -7,9 +7,12 @@ import ManageSearchIcon from '@mui/icons-material/ManageSearch';
 import Link from "react-router-dom/es/Link";
 import {useState} from "react";
 import '../scss/Header.scss';
+import {actionAuthLogout} from "../reducers/AuthReducer";
+import {connect} from "react-redux";
 
-const pages = ['catalog', 'about us', 'our team', 'faq', 'contact'];
-const settings = ['Profile', 'Account', 'Dashboard', 'Logout'];
+const pages = ['catalog', 'about us', 'our team', 'faq', 'contact']
+const settingsDefaultUserAuth = ['Profile', 'Logout']
+// const settingsAdmin = ['Profile', 'Dashboard', 'Logout']
 
 const Header = () => {
     const [anchorElNav, setAnchorElNav] = useState(null);
@@ -92,12 +95,75 @@ const Header = () => {
             </>
         )
     }
+    const ItemAuth = ({link, text}) => {
+        return (
+            <Link style={{textDecoration: 'none', color: '#000'}} to={`/${link}`}>
+                <MenuItem key={text} onClick={handleCloseNavMenu}>
+                    <Typography textAlign="center" color='#fff'>{text}</Typography>
+                </MenuItem>
+            </Link>
+        )
+    }
+
+    const UserIcon = ({auth}) => {
+        return (
+                !localStorage.authToken ?
+                    <Link style={{textDecoration: 'none'}} to={'/my-account'}>
+                        <IconButton sx={{ p: 0 }}>
+                            <Avatar alt="User" src="/static/images/avatar/2.jpg" />
+                        </IconButton>
+                    </Link> :
+                    <>
+                        <Tooltip title="Open settings">
+                            <IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
+                                <Avatar alt="User" src="/static/images/avatar/2.jpg" />
+                            </IconButton>
+                        </Tooltip>
+                        <Menu
+                            sx={{ mt: '55px', ml: '90%'}}
+                            id="menu-appbar"
+                            anchorEl={anchorElUser}
+                            anchorOrigin={{
+                                vertical: 'top',
+                                horizontal: 'right',
+                            }}
+                            keepMounted
+                            transformOrigin={{
+                                vertical: 'top',
+                                horizontal: 'right',
+                            }}
+                            open={Boolean(anchorElUser)}
+                            onClose={handleCloseUserMenu}
+                        >
+                            {settingsDefaultUserAuth.map(item => {
+                                if (item === 'Logout') {
+                                    return <CButtonLogOut/>
+                                }
+                                return <ItemAuth text={item}
+                                                 link={Array.from(item.toLowerCase()).map(i => i === ' ' ? '-' : i).join('')}/>
+                            })}
+                        </Menu>
+                    </>
+        )
+    }
+    const CUserIcon = connect(state => ({auth: state.auth}))(UserIcon)
+
+    const ButtonLogOut = ({actionLogOut}) => {
+        return (
+            <Button
+                onClick={() => {actionLogOut(); handleCloseNavMenu()}}
+                style={{textDecoration: 'none', color: '#fff', textAlign: "center", width: '100%'}}
+            >
+                Log out
+            </Button>
+        )
+    }
+    const CButtonLogOut = connect(null, {actionLogOut: actionAuthLogout})(ButtonLogOut)
 
     return (
         <AppBar sx={{padding: '10px 0', backgroundColor: 'rgb(131,179,175)'}} className='Header' position="fixed" enableColorOnDark={true}>
             <Container maxWidth="xl">
                 <Toolbar disableGutters>
-                    {/*Основной логотип*/}
                     <Typography
                         variant="h6"
                         noWrap
@@ -107,7 +173,6 @@ const Header = () => {
                         <LogoItem/>
                     </Typography>
 
-                    {/*Меню бургер*/}
                     <Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
                         <IconButton
                             size="large"
@@ -148,7 +213,6 @@ const Header = () => {
                         </Menu>
                     </Box>
 
-                    {/*Логитип для смартфонов*/}
                     <Typography
                         variant="h6"
                         noWrap
@@ -158,51 +222,22 @@ const Header = () => {
                         <LogoItem />
                     </Typography>
 
-                    {/*Основные ссылки для декстопа*/}
                     <Box className='Header__Links' sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
                         {pages.map((page) => (
                             <LinkItem page={page} color={'#fff'}/>
                         ))}
                     </Box>
 
-                    {/*Иконки для декстопа*/}
                     <Box className='Header__Icons' sx={{ flexGrow: 0, display: { xs: 'none', md: 'flex' } }}>
                         <IconItems size={'large'}/>
                     </Box>
-
-                    {/*Пользователь*/}
                     <Box sx={{ flexGrow: 0 }}>
-                        <Tooltip title="Open settings">
-                            <IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
-                                <Avatar alt="User" src="/static/images/avatar/2.jpg" />
-                            </IconButton>
-                        </Tooltip>
-                        <Menu
-                            sx={{ mt: '45px' }}
-                            id="menu-appbar"
-                            anchorEl={anchorElUser}
-                            anchorOrigin={{
-                                vertical: 'top',
-                                horizontal: 'right',
-                            }}
-                            keepMounted
-                            transformOrigin={{
-                                vertical: 'top',
-                                horizontal: 'right',
-                            }}
-                            open={Boolean(anchorElUser)}
-                            onClose={handleCloseUserMenu}
-                        >
-                            {settings.map((setting) => (
-                                <MenuItem key={setting} onClick={handleCloseNavMenu}>
-                                    <Typography textAlign="center">{setting}</Typography>
-                                </MenuItem>
-                            ))}
-                        </Menu>
+                        <CUserIcon/>
                     </Box>
                 </Toolbar>
             </Container>
         </AppBar>
     );
 };
+
 export default Header;

+ 113 - 0
src/components/MainButton.jsx

@@ -0,0 +1,113 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import ButtonUnstyled, { buttonUnstyledClasses } from '@mui/base/ButtonUnstyled';
+import { styled } from '@mui/system';
+
+const ButtonRoot = React.forwardRef(function ButtonRoot(props, ref) {
+    const { children, ...other } = props;
+
+    return (
+        <svg width="150" height="50" {...other} ref={ref}>
+            <polygon points="0,50 0,0 150,0 150,50" className="bg" />
+            <polygon points="0,50 0,0 150,0 150,50" className="borderEffect" />
+            <foreignObject x="0" y="0" width="150" height="50">
+                <div className="content">{children}</div>
+            </foreignObject>
+        </svg>
+    );
+});
+
+ButtonRoot.propTypes = {
+    children: PropTypes.node,
+};
+
+const CustomButtonRoot = styled(ButtonRoot)(
+    ({ theme }) => `
+  overflow: visible;
+  cursor: pointer;
+  --main-color: ${
+        theme.palette.mode === 'light' ? 'rgb(25,118,210)' : 'rgb(144,202,249)'
+    };
+  --hover-color: ${
+        theme.palette.mode === 'light'
+            ? 'rgba(25,118,210,0.04)'
+            : 'rgba(144,202,249,0.08)'
+    };
+  --active-color: ${
+        theme.palette.mode === 'light'
+            ? 'rgba(25,118,210,0.12)'
+            : 'rgba(144,202,249,0.24)'
+    };
+
+  & polygon {
+    fill: transparent;
+    transition: all 800ms ease;
+    pointer-events: none;
+  }
+  
+  & .bg {
+    stroke: var(--main-color);
+    stroke-width: 0.5;
+    filter: drop-shadow(0 4px 20px rgba(0, 0, 0, 0.1));
+    fill: transparent;
+  }
+
+  & .borderEffect {
+    stroke: var(--main-color);
+    stroke-width: 2;
+    stroke-dasharray: 150 600;
+    stroke-dashoffset: 150;
+    fill: transparent;
+  }
+
+  &:hover,
+  &.${buttonUnstyledClasses.focusVisible} {
+    .borderEffect {
+      stroke-dashoffset: -600;
+    }
+
+    .bg {
+      fill: var(--hover-color);
+    }
+  }
+
+  &:focus,
+  &.${buttonUnstyledClasses.focusVisible} {
+    outline: none;
+  }
+
+  &.${buttonUnstyledClasses.active} { 
+    & .bg {
+      fill: var(--active-color);
+      transition: fill 300ms ease-out;
+    }
+  }
+
+  & foreignObject {
+    pointer-events: none;
+
+    & .content {
+      font-family: Helvetica, Inter, Arial, sans-serif;
+      font-size: 14px;
+      font-weight: 200;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: var(--main-color);
+      text-transform: uppercase;
+    }
+
+    & svg {
+      margin: 0 5px;
+    }
+  }`,
+);
+
+const SvgButton = React.forwardRef(function SvgButton(props, ref) {
+    return <ButtonUnstyled {...props} component={CustomButtonRoot} ref={ref} />;
+});
+
+export default function UnstyledButtonCustom({text, onClick, disabled}) {
+    return <SvgButton disabled={disabled} onClick={onClick}>{text}</SvgButton>;
+}

+ 218 - 0
src/pages/MyAccountPage.jsx

@@ -0,0 +1,218 @@
+import Header from "../components/Header";
+import Footer from "../components/Footer";
+import Breadcrumb from "../components/Breadcrumbs";
+import {
+    Box,
+    Button,
+    Container,
+    FormControl,
+    OutlinedInput,
+    TextField,
+    Typography,
+    useMediaQuery
+} from "@mui/material";
+import UnstyledButtonCustom from "../components/MainButton";
+import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
+import {useState} from "react";
+import Link from "react-router-dom/es/Link";
+import {connect} from 'react-redux';
+import {actionFullLogin, actionFullRegister} from "../actions/ActionLogin";
+import Redirect from "react-router-dom/es/Redirect";
+
+const MyAccountPage = ({auth, promise, onLogin, onRegister}) => {
+    const matches = useMediaQuery('(max-width:899px)')
+    const [status, setStatus] = useState('login')
+
+    const Login = ({onLogin}) => {
+        const [valueLogin, setValueLogin] = useState('')
+        const [valuePassword, setValuePassword] = useState('')
+        const [flagLogin, setFlagLog] = useState(false)
+        const [flagPassword, setFlagPass] = useState(false)
+
+        return (
+            <Box
+                sx={{
+                    backgroundColor: "#fff",
+                    display: "flex",
+                    flexDirection: "column",
+                    justifyContent: "center",
+                    alignItems: "center",
+                    padding: "40px 60px"
+                }}
+            >
+                <Typography
+                    variant='h4'
+                    letterSpacing='10px'
+                    marginBottom='30px'
+                >
+                    LOGIN
+                </Typography>
+                <TextField
+                    id="login"
+                    label="Login"
+                    variant="standard"
+                    type="text"
+                    required
+                    fullWidth
+                    value={valueLogin}
+                    onChange={e => {setValueLogin(e.target.value); setFlagLog(true)}}
+                    error={flagLogin && (valueLogin.length <= 3 || valueLogin.length > 20)}
+                    sx={{marginBottom: '20px'}}
+                />
+                <TextField
+                    id="password"
+                    label="Password"
+                    variant="standard"
+                    type="password"
+                    required
+                    fullWidth
+                    value={valuePassword}
+                    onChange={e => {setValuePassword(e.target.value); setFlagPass(true)}}
+                    error={flagPassword && (valuePassword.length <= 3 || valuePassword.length > 20)}
+                    sx={{marginBottom: '50px'}}
+                />
+                {promise?.login?.status === "RESOLVED" &&
+                    <Typography
+                        variant='body2'
+                        color='red'
+                        marginBottom='20px'
+                    >
+                        This user does not exist
+                    </Typography>
+                }
+                <UnstyledButtonCustom
+                    onClick={() => {
+                        if(valuePassword.length >= 3 && valuePassword.length <= 20 &&
+                            valueLogin.length >= 3 && valueLogin.length <= 20) {
+                            onLogin(valueLogin, valuePassword)
+                        }
+                    }}
+                    text={'LOGIN'}
+                />
+                <Box
+                    width='100%'
+                    display='flex'
+                    justifyContent='flex-end'
+                    marginTop='20px'
+                >
+                    <Button
+                        onClick={() => setStatus('register')}
+                    >
+                        Register
+                        <ArrowForwardIosIcon/>
+                    </Button>
+                </Box>
+            </Box>
+        )
+    }
+    const Register = ({onRegister}) => {
+        const [valueLogin, setValueLogin] = useState('')
+        const [valuePassword, setValuePassword] = useState('')
+        const [flagLogin, setFlagLog] = useState(false)
+        const [flagPassword, setFlagPass] = useState(false)
+
+        return (
+            <Box
+                sx={{
+                    backgroundColor: "#fff",
+                    display: "flex",
+                    flexDirection: "column",
+                    justifyContent: "center",
+                    alignItems: "center",
+                    padding: "40px 60px"
+                }}
+            >
+                <Typography
+                    variant='h4'
+                    letterSpacing='10px'
+                    marginBottom='30px'
+                >
+                    REGISTER
+                </Typography>
+                <TextField
+                    id="login"
+                    label="Login"
+                    variant="standard"
+                    type="text"
+                    required
+                    fullWidth
+                    value={valueLogin}
+                    onChange={e => {setValueLogin(e.target.value); setFlagLog(true)}}
+                    error={flagLogin && (valueLogin.length <= 3 || valueLogin.length > 20)}
+                    sx={{marginBottom: '20px'}}
+                />
+                <TextField
+                    id="password"
+                    label="Password"
+                    variant="standard"
+                    type="password"
+                    required
+                    fullWidth
+                    value={valuePassword}
+                    onChange={e => {setValuePassword(e.target.value); setFlagPass(true)}}
+                    error={flagPassword && (valuePassword.length <= 3 || valuePassword.length > 20)}
+                    sx={{marginBottom: '50px'}}
+                />
+                <Typography
+                    variant='body2'
+                    color='#616161'
+                    textAlign='justify'
+                    marginBottom='40px'
+                >
+                    Your personal data will be used to support your experience throughout this website, to manage access to your account, and for other purposes described in our
+                    <Link style={{textDecoration: 'none'}} to='/privacy-policy'> privacy policy</Link>
+                    .
+                </Typography>
+                {promise?.register?.status === "RESOLVED" &&
+                    <Typography
+                        variant='body2'
+                        color='red'
+                        marginBottom='20px'
+                    >
+                        Such user already exists
+                    </Typography>
+                }
+                <UnstyledButtonCustom
+                    onClick={() => {
+                        if(valuePassword.length >= 3 && valuePassword.length <= 20 &&
+                            valueLogin.length >= 3 && valueLogin.length <= 20){
+                            onRegister(valueLogin, valuePassword)
+                        }
+                    }}
+                    text={'REGISTER'}
+                />
+                <Box
+                    width='100%'
+                    display='flex'
+                    justifyContent='flex-end'
+                    marginTop='20px'
+                >
+                    <Button
+                        onClick={() => setStatus('login')}
+                    >
+                        Login
+                        <ArrowForwardIosIcon/>
+                    </Button>
+                </Box>
+            </Box>
+        )
+    }
+
+    return (
+        <>
+            <Header/>
+            <Breadcrumb links={['my account']}/>
+            <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0"}}>
+                <Container maxWidth="sm">
+                    {auth?.payload ? <Redirect to={'/profile'}/> :
+                        status === 'login' ? <Login onLogin={onLogin}/> : <Register onRegister={onRegister}/>
+                    }
+                </Container>
+            </main>
+            <Footer/>
+        </>
+    )
+}
+const CLoginForm = connect(state => ({auth: state.auth, promise: state.promise}), {onLogin: actionFullLogin, onRegister: actionFullRegister})(MyAccountPage)
+
+export default CLoginForm

+ 15 - 0
src/pages/ProfilePage.jsx

@@ -0,0 +1,15 @@
+import Header from "../components/Header";
+import Footer from "../components/Footer";
+import Breadcrumb from "../components/Breadcrumbs";
+
+const ProfilePage = () => {
+    return (
+        <>
+            <Header/>
+            <Breadcrumb links={['Profile']}/>
+
+            <Footer/>
+        </>
+    )
+}
+export default ProfilePage

+ 38 - 0
src/reducers/AuthReducer.js

@@ -0,0 +1,38 @@
+const jwtDecode = token => {
+    try {
+        let arrToken = token.split('.')
+        let base64Token = atob(arrToken[1])
+        return JSON.parse(base64Token)
+    }
+    catch (e) {
+        console.log(e.message());
+    }
+}
+
+export const AuthReducer = (state, { type, token }) => {
+    if (!state) {
+        if (localStorage.authToken) {
+            type = 'AUTH_LOGIN'
+            token = localStorage.authToken
+        } else state = {}
+    }
+    if (type === 'AUTH_LOGIN') {
+        localStorage.setItem('authToken', token)
+        let payload = jwtDecode(token)
+        if (typeof payload === 'object') {
+            return {
+                ...state,
+                token,
+                payload
+            }
+        } else return state
+    }
+    if (type === 'AUTH_LOGOUT') {
+        localStorage.removeItem('authToken')
+        return {}
+    }
+    return state
+}
+
+export const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
+export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })

+ 44 - 0
src/reducers/CartReducer.js

@@ -0,0 +1,44 @@
+export const CartReducer = (state = {}, { type, good = {}, count = 1 }) => {
+    const { _id } = good
+    const types = {
+        CART_ADD() {
+            count = +count
+            if (!count) return state
+            return {
+                ...state,
+                [_id]: {
+                    good,
+                    count: count + (state[_id]?.count || 0)
+                }
+            }
+        },
+        CART_CHANGE() {
+            count = +count;
+            if (!count){
+                return state
+            }
+            return {
+                ...state,
+                [_id]: {good, count}
+            }
+        },
+        CART_REMOVE() {
+            let { [_id]: remove, ...newState } = state
+            return {
+                ...newState
+            }
+        },
+        CART_CLEAR() {
+            return {}
+        },
+    }
+    if (type in types) {
+        return types[type]()
+    }
+    return state
+}
+
+const actionCartAdd = (good, count=1) => ({type: "CART_ADD", good, count});
+const actionCardChange = (good, count) => ({type: 'CART_CHANGE', good, count})
+const actionCardRemove = (good) => ({type: 'CART_REMOVE', good})
+const actionCardClear = () => ({type: 'CART_CLEAR'})

+ 25 - 0
src/reducers/PromiseReducer.js

@@ -0,0 +1,25 @@
+export const PromiseReducer = (state = {}, { type, status, payload, error, name }) => {
+    if (type === 'PROMISE') {
+        return {
+            ...state,
+            [name]: { status, payload, error }
+        }
+    }
+    return state
+}
+
+const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
+const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
+const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
+
+export const actionPromise = (name, promise) =>
+    async dispatch => {
+        dispatch(actionPending(name))
+        try {
+            let data = await promise
+            dispatch(actionResolved(name, data))
+            return data
+        } catch (error) {
+            dispatch(actionRejected(name, error))
+        }
+    }

+ 15 - 0
src/reducers/index.js

@@ -0,0 +1,15 @@
+import {applyMiddleware, combineReducers, createStore} from "redux";
+import {AuthReducer} from "./AuthReducer";
+import {PromiseReducer} from "./PromiseReducer";
+import {CartReducer} from "./CartReducer";
+import thunk from "redux-thunk";
+
+const rootReducer = combineReducers({
+    auth: AuthReducer,
+    promise: PromiseReducer,
+    cart: CartReducer
+})
+
+export const store = createStore(rootReducer, applyMiddleware(thunk))
+store.subscribe(() => console.log(store.getState()))
+

+ 18 - 0
src/scss/Header.scss

@@ -105,3 +105,21 @@
     }
   }
 }
+#menu-appbar {
+  .MuiPaper-root {
+    background-color: rgba(0, 0, 0, 0.8);
+    li {
+      display: flex;
+      justify-content: center;
+      max-height: 40px;
+      width: 100%;
+      button {
+        text-align: center;
+      }
+      &:last-child{
+        display: flex;
+        justify-content: space-around;
+      }
+    }
+  }
+}