diff --git a/app.js b/app.js
index 70c9b20..9beabb8 100644
--- a/app.js
+++ b/app.js
@@ -15,7 +15,7 @@ const app = express()
app.use(express.json({ extended: true }))
app.use('/api/auth', require('./routes/auth.routes'))
-app.use('/api/server', require('./routes/phpserver.routes'))
+app.use('/api/notes', require('./routes/notes.routes'))
if (httpsRedirect) app.use(httpToHttps)
diff --git a/client/package-lock.json b/client/package-lock.json
index 47967ef..7b30a77 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -3601,15 +3601,6 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"optional": true
},
- "bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "optional": true,
- "requires": {
- "file-uri-to-path": "1.0.0"
- }
- },
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -5218,11 +5209,18 @@
}
},
"domhandler": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
- "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz",
+ "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==",
"requires": {
- "domelementtype": "1"
+ "domelementtype": "^2.2.0"
+ },
+ "dependencies": {
+ "domelementtype": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
+ "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
+ }
}
},
"domutils": {
@@ -6575,12 +6573,6 @@
}
}
},
- "file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "optional": true
- },
"filesize": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
@@ -7219,6 +7211,19 @@
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
},
+ "history": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
+ "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^3.0.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^1.0.1"
+ }
+ },
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -7229,6 +7234,14 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
"hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -7362,22 +7375,40 @@
}
},
"htmlparser2": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
- "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
- "requires": {
- "domelementtype": "^1.3.1",
- "domhandler": "^2.3.0",
- "domutils": "^1.5.1",
- "entities": "^1.1.1",
- "inherits": "^2.0.1",
- "readable-stream": "^3.1.1"
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
+ "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0",
+ "domutils": "^2.5.2",
+ "entities": "^2.0.0"
},
"dependencies": {
- "entities": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
- "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
+ "dom-serializer": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
+ "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.0",
+ "entities": "^2.0.0"
+ }
+ },
+ "domelementtype": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
+ "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
+ },
+ "domutils": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
+ "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
+ "requires": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.2.0",
+ "domhandler": "^4.2.0"
+ }
}
}
},
@@ -10354,6 +10385,15 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
},
+ "mini-create-react-context": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
+ "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "tiny-warning": "^1.0.3"
+ }
+ },
"mini-css-extract-plugin": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz",
@@ -10545,12 +10585,6 @@
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
},
- "nan": {
- "version": "2.14.2",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
- "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
- "optional": true
- },
"nanoid": {
"version": "3.1.22",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz",
@@ -12849,6 +12883,52 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
"integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg=="
},
+ "react-router": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
+ "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "history": "^4.9.0",
+ "hoist-non-react-statics": "^3.1.0",
+ "loose-envify": "^1.3.1",
+ "mini-create-react-context": "^0.4.0",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.2",
+ "react-is": "^16.6.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ }
+ }
+ },
+ "react-router-dom": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
+ "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "history": "^4.9.0",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.6.2",
+ "react-router": "5.2.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ }
+ },
"react-scripts": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz",
@@ -13202,28 +13282,65 @@
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
},
"renderkid": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.5.tgz",
- "integrity": "sha512-ccqoLg+HLOHq1vdfYNm4TBeaCDIi1FLt3wGojTDSvdewUv65oTmI3cnT2E4hRjl1gzKZIPK+KZrXzlUYKnR+vQ==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.6.tgz",
+ "integrity": "sha512-GIis2GBr/ho0pFNf57D4XM4+PgnQuTii0WCPjEZmZfKivzUfGuRdjN2aQYtYMiNggHmNyBve+thFnNR1iBRcKg==",
"requires": {
- "css-select": "^2.0.2",
- "dom-converter": "^0.2",
- "htmlparser2": "^3.10.1",
- "lodash": "^4.17.20",
- "strip-ansi": "^3.0.0"
+ "css-select": "^4.1.3",
+ "dom-converter": "^0.2.0",
+ "htmlparser2": "^6.1.0",
+ "lodash": "^4.17.21",
+ "strip-ansi": "^6.0.0"
},
"dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ "css-select": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
+ "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==",
+ "requires": {
+ "boolbase": "^1.0.0",
+ "css-what": "^5.0.0",
+ "domhandler": "^4.2.0",
+ "domutils": "^2.6.0",
+ "nth-check": "^2.0.0"
+ }
},
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "css-what": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz",
+ "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg=="
+ },
+ "dom-serializer": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
+ "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
"requires": {
- "ansi-regex": "^2.0.0"
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.0",
+ "entities": "^2.0.0"
+ }
+ },
+ "domelementtype": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
+ "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
+ },
+ "domutils": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
+ "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
+ "requires": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.2.0",
+ "domhandler": "^4.2.0"
+ }
+ },
+ "nth-check": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
+ "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
+ "requires": {
+ "boolbase": "^1.0.0"
}
}
}
@@ -13359,6 +13476,11 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
},
+ "resolve-pathname": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
+ "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
+ },
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -15033,6 +15155,16 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
},
+ "tiny-invariant": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
+ "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
+ },
+ "tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
"tmpl": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
@@ -15609,6 +15741,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "value-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
+ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -15816,11 +15953,7 @@
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "optional": true,
- "requires": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- }
+ "optional": true
},
"glob-parent": {
"version": "3.1.0",
@@ -16419,11 +16552,7 @@
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "optional": true,
- "requires": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- }
+ "optional": true
},
"glob-parent": {
"version": "3.1.0",
diff --git a/client/package.json b/client/package.json
index 15de38f..e13a192 100644
--- a/client/package.json
+++ b/client/package.json
@@ -12,6 +12,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-markdown": "^6.0.2",
+ "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"react-stack-grid": "^0.7.1",
"react-textarea-autosize": "^8.3.3",
diff --git a/client/src/App.css b/client/src/App.css
index cde4a48..d3cca56 100644
--- a/client/src/App.css
+++ b/client/src/App.css
@@ -1,48 +1,3 @@
-.App-logo {
- height: 3em;
- pointer-events: none;
- user-select: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-.card:hover {
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
-}
-
-.card {
- transition: box-shadow 0.3s;
-}
-
-.card-text > h1 {
- font-size: 1.625em;
-}
-.card-text > h2 {
- font-size: 1.5em;
-}
-.card-text > h3 {
- font-size: 1.375em;
-}
-.card-text > h4 {
- font-size: 1.25em;
-}
-.card-text > h5 {
- font-size: 1.125em;
-}
-
.lds-dual-ring {
display: inline-block;
height: 1em;
@@ -68,12 +23,3 @@
transform: rotate(360deg);
}
}
-
-@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap");
-
-.brand {
- font-family: "Roboto", sans-serif;
- padding-top: 0.2rem !important;
- pointer-events: none;
- user-select: none;
-}
diff --git a/client/src/App.js b/client/src/App.js
index d988dd3..99f523d 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -1,267 +1,47 @@
import React from 'react';
-import logo from './Shared/Logo/logo.svg';
-import './App.css';
-import CardList from './Cards/CardList'
-import AddCard from './Cards/AddCard'
-import Context from './context'
-import Loader from './Shared/Loader'
-import ModalCardEdit from './Cards/ModalCardEdit'
-import ModalLogin from './Login/ModalLogin'
-import DataService from './Services/DataService'
-import Card, { checkCardsArr } from './Cards/cardType/Card'
-//import useDebouncedEffect from './Shared/useDebouncedEffect.hook'
-
-const { loadData, postData, updDataServLogin } = DataService()
-
-const brandText = "Notes"
-
-var cardCount = 0
-
-function calcCount(cards) {
- let id = cardCount;
- [...cards].forEach(element => {
- if (Number(element.id) >= id) id = Number(element.id)
- });
- return id
-}
-function useCardsArr(defaultValue) {
- const [value, setValue] = React.useState(defaultValue)
+import './App.css';
- function trySetValue(cardsArr) {
- if (checkCardsArr(cardsArr) || cardsArr === null) setValue(cardsArr)
- else console.error('Массив cardsArr не прошел проверку \n', cardsArr)
- }
+import Loader from './shared/Loader'
- return [value, trySetValue]
-}
-
-function useUpdater() {
- const [updaterVal, setUpdaterVal] = React.useState(null)
- const timer = React.useRef()
- if (timer.current) clearTimeout(timer.current)
- timer.current = setTimeout(() => {
- console.log("Timed update")
- setUpdaterVal(Date.now())
- }, 60 * 1000) // обновяем через минуту
- return [updaterVal]
-}
+import { BrowserRouter as Router } from 'react-router-dom'
+import { useRoutes } from './routes'
+import { useAuth } from './hooks/auth.hook'
+import { AuthContext } from './context/AuthContext'
+import { PageContext } from './context/PageContext'
+import Header from './pages/SharedComponents/Header'
function App() {
- const [cardsArr, setCards] = useCardsArr(null)
- const [editCardId, setEditCardId] = React.useState(null)
- const [loading, setLoading] = React.useState({ state: false, res: false })
+ const { token, login, logout, userId, email, ready } = useAuth()
+ const isAuthenticated = !!token
+ const routes = useRoutes(isAuthenticated)
- const [openLogin, setOpenLogin] = React.useState(false)
- const [logged, setLogged] = React.useState(false)
- const [userName, setUserName] = React.useState(undefined)
+ const [nav, setNav] = React.useState()
- const [updaterVal] = useUpdater()
-
- React.useEffect(loadDataFromServer, [logged, userName, updaterVal]) // eslint-disable-line react-hooks/exhaustive-deps
- //useDebouncedEffect(loadDataToServer, [cardsArr], 300) // eslint-disable-line react-hooks/exhaustive-deps
- React.useEffect(loadDataToServer, [cardsArr]) // eslint-disable-line react-hooks/exhaustive-deps
- React.useEffect(clearOldData, [logged, userName]) // eslint-disable-line react-hooks/exhaustive-deps
-
- ///////////
- function onLogin(login) {
- //console.log("onLogin", login)
- return new Promise((res, rej) => {
- onLogout()
- .then(() => {
- //console.log("onLogin", login, "onLogout.then")
- updDataServLogin(login)
- .then(r => {
- //console.log("onLogin", login, "onLogout.then", "updDataServLogin.then")
- setLogged(Boolean(r))
- setUserName(login)
- res(r)
- })
- .catch(e => {
- //console.log("onLogin", login, "onLogout.then", "updDataServLogin.catch", e)
- rej(e)
- })
- })
- .catch(e => console.log("logout catch in onLogin", e))
- })
- }
-
- function onLogout() {
- //console.log("onLogout")
- return new Promise((res) => {
- updDataServLogin(null)
- .finally(() => {
- if (logged) {
- //console.log("onLogout - was logged, dislogin")
- setUserName(undefined)
- setLogged(false)
- } else {
- //console.log("onLogout - also not logged")
- clearOldData()
- }
- })
- .finally(res)
- .catch(e => console.log("Data service dislogin failed", e))
- })
- }
-
- function clearOldData() {
- //console.log("clearOldData, logged:", logged)
- if (!logged) setCards(null)
- }
- ///////////
-
- ///////////
- function loadDataToServer() {
- try {
- let startUsername = userName
- if (logged && userName) postData(cardsArr)
- .then(res => {
- if (!cardsArr) console.log("empty post!")
- console.log('[onPostData]', res)
- if (startUsername !== userName) {
- console.log("упс, несостыковочка с userName");
- loadDataToServer()
- }
- })
- .catch(e => console.log(`Data post request error. Response: ${e}`))
- }
- catch (e) {
- console.error(e)
- }
- }
-
- function loadDataFromServer() {
- try {
- let startUsername = userName
- if (logged && userName) {
- setLoading({ state: true, res: loading.res })
- loadData()
- .then(data => {
- console.log('[onLoadData]', 'Данные с сервера загружены')
- if (startUsername === userName) setLoadedCards(data)
- else console.log("упс, несостыковочка с userName");
- setLoading({ state: false, res: true })
- })
- .catch(e => {
- console.log(`Data load request error. Response:`, e)
- setLoading({ state: false, res: false })
- })
- }
- }
- catch (e) {
- setLoading({ state: false, res: false })
- console.error(e)
- }
- }
- ///////////
-
- ///////////
- function setLoadedCards(cards) {
- setCards([...cards])
- cardCount = calcCount(cards)
- }
-
- function removeCard(index) {
- cardsArr.splice(index, 1)
- setCards([...cardsArr])
- }
-
- function deleteAll() {
- setCards([])
- }
-
- function addCard(cardData = {}) {
- const newCard = new Card({ id: ++cardCount, name: cardData.name, color: cardData.color, text: cardData.text })
- setCards(
-
- (cardsArr != null) ? cardsArr.concat([newCard]) : [newCard]
+ if (!ready) {
+ return (
+
+
+
)
}
- function changeCardColor(index, color) {
- cardsArr[index].color = color
- setCards([...cardsArr])
- }
-
- function editCardContent(index, name, text) {
- if (cardsArr[index]) {
- let card = new Card(cardsArr[index])
- card.name = name
- card.text = text
- cardsArr[index] = card
- }
- setCards([...cardsArr])
- }
- ///////////
-
- ///////////
- function getCardByIndex(index) {
- return index !== null ? cardsArr[index] : null
- }
- function setEditCard(index) {
- setEditCardId(index)
- }
- function unsetEditCard() {
- setEditCardId(null)
- }
- ///////////
return (
-
-
-
-
-
-
-
-
- {cardsArr && cardsArr.length ? (
-
- ) : (loading.state || !loading.res) ? null : logged ? (
-
-
No cards. You can add a new one!
-
- ) : (
-
- )}
-
- {(logged && !loading.state && !loading.res) && (
-
- )}
-
- {loading.state &&
-
-
-
- }
-
-
-
- );
+
+
+
+
+
+ {routes}
+
+
+
+
+
+ )
}
export default App;
diff --git a/client/src/Cards/AddCard.js b/client/src/Cards/AddCard.js
index d5bbf4a..98451a9 100644
--- a/client/src/Cards/AddCard.js
+++ b/client/src/Cards/AddCard.js
@@ -1,10 +1,11 @@
import React from 'react'
-import PropTypes from 'prop-types'
import TextareaAutosize from 'react-textarea-autosize'
import Palette, { colors } from './palette/palette'
-import useInputValue from '../Shared/useInputValue.hook'
+import useInputValue from '../hooks/useInputValue.hook'
+import CardsContext from '../context/CardsContext'
-function AddCard({ onCreate, onDeleteAll }) {
+function AddCard() {
+ const { addCard } = React.useContext(CardsContext)
const input = useInputValue('')
const defColor = colors[0]
@@ -25,7 +26,7 @@ function AddCard({ onCreate, onDeleteAll }) {
function submitHandler() {
if (String(input.value).trim() && String(color).trim()) {
- onCreate({ name: String(input.value).trim(), text: "", color: String(color) })
+ addCard({ name: String(input.value).trim(), text: "", color: String(color) })
input.clear()
setColor(defColor)
}
@@ -69,9 +70,4 @@ function AddCard({ onCreate, onDeleteAll }) {
)
}
-AddCard.propTypes = {
- onCreate: PropTypes.func.isRequired,
- onDeleteAll: PropTypes.func.isRequired
-}
-
export default AddCard
diff --git a/client/src/Cards/CardItem.js b/client/src/Cards/CardItem.js
index a245adf..d61181c 100644
--- a/client/src/Cards/CardItem.js
+++ b/client/src/Cards/CardItem.js
@@ -1,6 +1,6 @@
import React, { useContext } from 'react'
import PropTypes from 'prop-types'
-import Context from '../context'
+import CardsContext from '../context/CardsContext'
import Card, { PropTypeCard } from './cardType/Card'
import ReactMarkdown from 'react-markdown'
import gfm from 'remark-gfm'
@@ -10,7 +10,7 @@ function fixLineBreaks(mdStr) {
}
function CardItem({ card = new Card(), index }) {
- const { removeCard, setEditCard } = useContext(Context)
+ const { removeCard, setEditCard } = useContext(CardsContext)
const lineClip = 12
const bgColor = card.color
diff --git a/client/src/Cards/ModalCardEdit.js b/client/src/Cards/ModalCardEdit.js
index 685a91f..e5f4426 100644
--- a/client/src/Cards/ModalCardEdit.js
+++ b/client/src/Cards/ModalCardEdit.js
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Context from '../context'
+import CardsContext from '../context/CardsContext'
import TextareaAutosize from 'react-textarea-autosize'
-import Modal, { ModalProps } from "../Shared/Modal/Modal"
+import Modal, { ModalProps } from "../shared/Modal/Modal"
import Card, { PropTypeCard } from './cardType/Card'
import Palette from './palette/palette'
@@ -20,7 +20,7 @@ function calcMaxRows() {
}
function ModalCardEdit({ card = new Card(), index }) {
- const { removeCard, changeCardColor, unsetEditCard, editCardContent } = React.useContext(Context)
+ const { removeCard, changeCardColor, unsetEditCard, editCardContent } = React.useContext(CardsContext)
React.useEffect(() => { if (card !== null) open() }, [card])
const [showForm, setShowForm] = React.useState(false)
@@ -120,7 +120,7 @@ function ModalCardEdit({ card = new Card(), index }) {
- Id: {card && card.id}
+ Id {index}
diff --git a/client/src/Cards/cardType/Card.js b/client/src/Cards/cardType/Card.js
index 5566b48..33e87c0 100644
--- a/client/src/Cards/cardType/Card.js
+++ b/client/src/Cards/cardType/Card.js
@@ -1,10 +1,7 @@
import PropTypes from 'prop-types'
export const PropTypeCard = PropTypes.shape({
- id: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- ]),
+ id: PropTypes.string,
name: PropTypes.string,
color: PropTypes.string,
text: PropTypes.string,
@@ -12,7 +9,7 @@ export const PropTypeCard = PropTypes.shape({
export function checkCard(card) {
return (
- (typeof card.id === "number" || typeof card.id === "string") &&
+ (typeof card.id === "string") &&
typeof card.name === "string" &&
typeof card.color === "string" &&
typeof card.text === "string"
@@ -34,7 +31,7 @@ export function checkCardsArr(cardsArr) {
export class Card {
constructor({ id, name, color, text }) {
- this.id = Number(id)
+ this.id = String(id)
this.name = String(name)
this.color = String(color)
this.text = String(text)
diff --git a/client/src/Context/cardsContext.js b/client/src/Context/cardsContext.js
new file mode 100644
index 0000000..084eb5e
--- /dev/null
+++ b/client/src/Context/cardsContext.js
@@ -0,0 +1,5 @@
+import {createContext} from 'react'
+
+const CardsContext = createContext()
+
+export default CardsContext
\ No newline at end of file
diff --git a/client/src/Login/ModalLogin.js b/client/src/Login/ModalLogin.js
deleted file mode 100644
index da5ffd8..0000000
--- a/client/src/Login/ModalLogin.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import React from "react"
-import Modal, { ModalProps } from "../Shared/Modal/Modal"
-import PropTypes from 'prop-types'
-import useInputValue from '../Shared/useInputValue.hook'
-import LoginService from '../Services/LoginService'
-const { logIn, logOut } = LoginService()
-
-function checkUsername(str) {
- return str && typeof str === 'string' && str.length > 3 && str.length < 20 && str === validateUsername(str)
-}
-
-function validateUsername(str) {
- return String(str).replace(/@|;|:|\.|,|\/|\\|\||\$|\?|!|#|%|\*|\^|\+|=|\[|\]| |\\ |«|<|>/gi, "").trim()
-}
-
-function ModalLogin(props) {
- const { onLogin, onLogout, logged, userName, isOpen, setOpenState } = props
-
- const input = useInputValue("")
-
- const modalProps = new ModalProps()
- modalProps.isOpen = isOpen
- modalProps.setOpenState = setOpenState
- modalProps.sideClose = true
-
- const [labelAlert, setLabelAlert] = React.useState("");
-
- // eslint-disable-next-line no-unused-vars
- function open() {
- setOpenState(true)
- }
-
- function close() {
- clearInput()
- setLabelAlert("")
- setOpenState(false)
- }
-
- function clearInput() {
- input.clear()
- }
-
- function tryLogin() {
- if (checkUsername(input.value)) {
- logIn(input.value.trim())
- .then((name) => {
- onLogin(name)
- close()
- console.log('Login:', name)
- })
- .catch((err) => {
- console.log("Не удалось залогиниться")
- console.log(err)
- setLabelAlert(
- Не удалось залогиниться {err && {String(err)}}
-
)
- })
- } else if (input.value) {
- setLabelAlert(
{input.value.length > 3 ? "Исправьте логин" : "Слишком короткий логин"}
)
- } else {
- setLabelAlert(
Поле "Username" не должно быть пустым
)
- }
- }
-
- function tryLogout() {
- logOut()
- .then((name) => {
- onLogout(name)
- console.log('Logged out')
- })
- .catch((err) => {
- console.log("Не удалось разлогиниться")
- console.log(err)
- setLabelAlert(
- Не удалось разлогиниться {err && {String(err)}}
-
)
- })
- //close()
- }
-
- return (
-
-
-
-
-
- {logged ? AUTORISED : 'UNAUTORISED'}
-
- {userName ? {userName} : ``}
-
-
-
-
- {labelAlert}
-
- e.key === 'Enter' && tryLogin()}
- {...input.bind}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-ModalLogin.propTypes = {
- logged: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
- userName: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
- login: PropTypes.func,
- logout: PropTypes.func,
-
- isOpen: PropTypes.bool,
- setOpenState: PropTypes.func
-}
-
-export default ModalLogin
diff --git a/client/src/Services/DataService.js b/client/src/Services/DataService.js
deleted file mode 100644
index 01fc03c..0000000
--- a/client/src/Services/DataService.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import { ajax } from "jquery"
-import Card, { checkCardsArr } from '../Cards/cardType/Card'
-
-export default function DataService() {
- ////////////////////////////////////////////////////////////
- var user = null
- const baseUrl = '/api/server/'
-
- ////////////////////////////////////////////////////////////
- function updDataServLogin(login) {
- return new Promise((res, rej) => {
- if (login && typeof login === "string") {
- user = login
- //console.log("data serv login set")
- res(user)
- } else if (login === null) {
- user = null
- //console.log("data serv login unset")
- res()
- } else {
- //console.log("data serv cant set login", login)
- rej(login)
- }
- })
- }
- ////////////////////////////////////////////////////////////
-
- ////////////////////////////////////////////////////////////
- //let recuestCount = 1;
- function request(target, data) {
- //const rc = recuestCount++
- //console.log(` \nrequest ${rc} - "${target}" started \n params - user:"${user}" data:"${data}"`)
- return new Promise((res, rej) => {
- ajax({
- url: baseUrl,
- type: "POST",
- dataType: "html",
- data: {
- user: user,
- target: target,
- data: data,
- },
- })
- .done(resdata => res(resdata))
- .fail(rejdata => rej(rejdata))
- /*.always(() => console.log(
- `requested - newUser:"${user}" resolveData:"${data}" \n
- request ${rc} - "${target}" ended \n `
- ))*/
- })
- }
- ////////////////////////////////////////////////////////////
-
- ////////////////////////////////////////////////////////////
- function requestGetData() {
- return request('getData', null)
- }
-
- function requestPostData(data) {
- return request('setData', data || [])
- }
- ////////////////////////////////////////////////////////////
-
- ////////////////////////////////////////////////////////////
- function tryParce(str) {
- try {
- return JSON.parse(str);
- } catch (e) {
- return str;
- }
- }
-
- function checkData(data) {
- //console.log('start check data')
- try {
- if (data === null) console.log("null data");
- return data === null || data === [] || checkCardsArr(data)
- } catch {
- return false
- }
-
- }
- ////////////////////////////////////////////////////////////
-
- ////////////////////////////////////////////////////////////
- function loadData() {
- return new Promise((res, rej) => {
- try {
- (user === null
- ? Promise.reject(rej())
- : requestGetData())
- .then((d) => {
- let data = tryParce(d)//here we parce json
- //console.log("[DATA] from loadData(): ", data)
- if (!data) console.log("empty data from server");
- if (!checkData(data)) {
- console.error("[loadData] Bad data format")
- console.log(data)
- let checkDel = user !== null
- if (checkDel && window.confirm("Bad data: " + data + ". Delete data on server?")) {
- console.log('clear data')
- requestPostData([new Card({ id: 0, color: "orange", name: "Error", text: "Данные были очищены из за ошибки" })]).then(() => loadData().then(res, rej), rej)//очистка данных
- } else rej("Not format data" + !user ? " & unlogged" : "")
- } else {
- res(data || [])
- }
- })
- .catch(rej)
- } catch (e) {
- rej(e)
- console.error(e)
- }
- })
- }
-
- function postData(data) {
- return new Promise((res, rej) => {
- try {
- (user === null
- ? Promise.reject(rej())
- : loadData())
- .then((d) => {
- if (!data) console.log("empty data to post");
- if (!d) console.log("empty loaded to check");
- let pDat = data === null ? (d || []) : data
- if (!pDat) console.log("empty will be posted");
- requestPostData(pDat).then(res, rej)
- })
- .catch(rej)
- } catch (e) {
- rej(e)
- console.error(e)
- }
- })
- }
- ////////////////////////////////////////////////////////////
-
- return { loadData, postData, updDataServLogin }
-}
diff --git a/client/src/Services/LoginService.js b/client/src/Services/LoginService.js
deleted file mode 100644
index a900754..0000000
--- a/client/src/Services/LoginService.js
+++ /dev/null
@@ -1,31 +0,0 @@
-export default function DataService() {
- // ПОКА ЭТО ТУПО ЗАГЛУШКА
- var user = null
-
- ////////////////////////////////////////////////////////////
-
- function logIn(login) {
- return new Promise((res, rej) => {
- if (login && typeof login === "string") {
- user = login
- //console.log("login serv login set")
- res(user)
- } else {
- //console.log("login serv cant set login", login)
- rej(login)
- }
- })
- }
-
- function logOut() {
- return new Promise((res, rej) => {
- user = null
- //console.log("login serv dislogin")
- res()
- })
- }
-
- ////////////////////////////////////////////////////////////
-
- return { logIn, logOut }
-}
diff --git a/client/src/context.js b/client/src/context.js
deleted file mode 100644
index f015347..0000000
--- a/client/src/context.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react'
-
-const Context = React.createContext()
-
-export default Context
\ No newline at end of file
diff --git a/client/src/context/AuthContext.js b/client/src/context/AuthContext.js
new file mode 100644
index 0000000..b302288
--- /dev/null
+++ b/client/src/context/AuthContext.js
@@ -0,0 +1,12 @@
+import {createContext} from 'react'
+
+function noop() {}
+
+export const AuthContext = createContext({
+ token: null,
+ userId: null,
+ email: null,
+ login: noop,
+ logout: noop,
+ isAuthenticated: false
+})
diff --git a/client/src/context/PageContext.js b/client/src/context/PageContext.js
new file mode 100644
index 0000000..0410d50
--- /dev/null
+++ b/client/src/context/PageContext.js
@@ -0,0 +1,5 @@
+import { createContext } from 'react'
+
+function noop() { }
+
+export const PageContext = createContext({ setNav: noop })
diff --git a/client/src/hooks/auth.hook.js b/client/src/hooks/auth.hook.js
new file mode 100644
index 0000000..588548f
--- /dev/null
+++ b/client/src/hooks/auth.hook.js
@@ -0,0 +1,42 @@
+import {useState, useCallback, useEffect} from 'react'
+
+const storageName = 'userData'
+
+export const useAuth = () => {
+ const [token, setToken] = useState(null)
+ const [ready, setReady] = useState(false)
+ const [userId, setUserId] = useState(null)
+ const [email, setEmail] = useState(null)
+
+ const login = useCallback((jwtToken, id, email) => {
+ setToken(jwtToken)
+ setUserId(id)
+ setEmail(email)
+
+ localStorage.setItem(storageName, JSON.stringify({
+ userId: id, token: jwtToken, email: email
+ }))
+
+ }, [])
+
+
+ const logout = useCallback(() => {
+
+ setToken(null)
+ setUserId(null)
+ setEmail(null)
+ localStorage.removeItem(storageName)
+ }, [])
+
+ useEffect(() => {
+ const data = JSON.parse(localStorage.getItem(storageName))
+
+ if (data && data.token) {
+ login(data.token, data.userId, data.email)
+ }
+ setReady(true)
+ }, [login])
+
+
+ return { login, logout, token, userId, email, ready }
+}
diff --git a/client/src/hooks/http.hook.js b/client/src/hooks/http.hook.js
new file mode 100644
index 0000000..4c8fec0
--- /dev/null
+++ b/client/src/hooks/http.hook.js
@@ -0,0 +1,35 @@
+import {useState, useCallback} from 'react'
+
+export const useHttp = () => {
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState(null)
+
+ const request = useCallback(async (url, method = 'GET', body = null, headers = {}) => {
+ setLoading(true)
+ try {
+ if (body) {
+ body = JSON.stringify(body)
+ headers['Content-Type'] = 'application/json'
+ }
+
+ const response = await fetch(url, {method, body, headers})
+ const data = await response.json()
+
+ if (!response.ok) {
+ throw new Error(data.message || 'Что-то пошло не так')
+ }
+
+ setLoading(false)
+
+ return data
+ } catch (e) {
+ setLoading(false)
+ setError(e.message)
+ throw e
+ }
+ }, [])
+
+ const clearError = useCallback(() => setError(null), [])
+
+ return { loading, request, error, clearError }
+}
diff --git a/client/src/Shared/useDebouncedEffect.hook.js b/client/src/hooks/useDebouncedEffect.hook.js
similarity index 100%
rename from client/src/Shared/useDebouncedEffect.hook.js
rename to client/src/hooks/useDebouncedEffect.hook.js
diff --git a/client/src/Shared/useInputValue.hook.js b/client/src/hooks/useInputValue.hook.js
similarity index 100%
rename from client/src/Shared/useInputValue.hook.js
rename to client/src/hooks/useInputValue.hook.js
diff --git a/client/src/pages/AuthPage.css b/client/src/pages/AuthPage.css
new file mode 100644
index 0000000..1424231
--- /dev/null
+++ b/client/src/pages/AuthPage.css
@@ -0,0 +1,8 @@
+
+.form-body {
+ width: 100%;
+ max-width: 620px;
+ max-height: 100%;
+ overflow-y: auto;
+ height: fit-content;
+}
diff --git a/client/src/pages/AuthPage.js b/client/src/pages/AuthPage.js
new file mode 100644
index 0000000..9432fb0
--- /dev/null
+++ b/client/src/pages/AuthPage.js
@@ -0,0 +1,144 @@
+import React, { useContext, useEffect, useState } from 'react'
+import { useHttp } from '../hooks/http.hook'
+
+import { AuthContext } from '../context/AuthContext'
+import { PageContext } from '../context/PageContext'
+
+import { NavLink, useHistory } from 'react-router-dom'
+import './AuthPage.css'
+
+function AuthPage() {
+ const auth = useContext(AuthContext)
+ const page = useContext(PageContext)
+ const history = useHistory()
+
+ const { loading, request, error, clearError } = useHttp()
+ const [form, setForm] = useState({
+ email: '', password: ''
+ })
+ const [message, setMessage] = useState(null)
+
+ useEffect(() => {
+ if (error) setMessage([error, false])
+ clearError()
+ }, [error, clearError])
+
+ const changeHandler = event => {
+ setForm({ ...form, [event.target.name]: event.target.value })
+ }
+
+ const registerHandler = async () => {
+ try {
+ const data = await request('/api/auth/register', 'POST', { ...form })
+ if (data.message) setMessage([data.message, true])
+ } catch (e) { }
+ }
+
+ const loginHandler = async () => {
+ try {
+ const data = await request('/api/auth/login', 'POST', { ...form })
+ auth.login(data.token, data.userId, data.email)
+ history.push("/")
+ } catch (e) { }
+ }
+
+ const logoutHandler = event => {
+ event.preventDefault()
+ auth.logout()
+ }
+
+ React.useEffect(() => {
+ page.setNav(auth.isAuthenticated &&
+
+ К заметкам
+
+ )
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [auth.isAuthenticated, auth.token])
+
+ return (
+
+
+
+
+
+
Авторизация
+
+
+
+
+
+ {auth.isAuthenticated ? AUTORISED : 'UNAUTORISED'}
+
+ {auth.email ? {auth.email} : ``}
+
+
+
+
+ {
+ message &&
+
+ {message[1] ? "Инфо" : "Ошибка"}
+ {String(message[0])}
+
+ }
+
+
+
e.key === 'Enter' && loginHandler()}
+ />
+
+
+
e.key === 'Enter' && loginHandler()}
+ />
+
+
+
+ {!auth.isAuthenticated ? (
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ )
+}
+
+export default AuthPage
\ No newline at end of file
diff --git a/client/src/pages/CardsPage.css b/client/src/pages/CardsPage.css
new file mode 100644
index 0000000..76fa444
--- /dev/null
+++ b/client/src/pages/CardsPage.css
@@ -0,0 +1,23 @@
+.card:hover {
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+}
+
+.card {
+ transition: box-shadow 0.3s;
+}
+
+.card-text > h1 {
+ font-size: 1.625em;
+}
+.card-text > h2 {
+ font-size: 1.5em;
+}
+.card-text > h3 {
+ font-size: 1.375em;
+}
+.card-text > h4 {
+ font-size: 1.25em;
+}
+.card-text > h5 {
+ font-size: 1.125em;
+}
diff --git a/client/src/pages/CardsPage.js b/client/src/pages/CardsPage.js
new file mode 100644
index 0000000..62cf3e9
--- /dev/null
+++ b/client/src/pages/CardsPage.js
@@ -0,0 +1,206 @@
+import React from 'react';
+
+import './CardsPage.css';
+import CardList from '../Cards/CardList'
+import AddCard from '../Cards/AddCard'
+import CardsContext from '../context/CardsContext'
+import Loader from '../shared/Loader'
+import ModalCardEdit from '../Cards/ModalCardEdit'
+
+import Card, { checkCardsArr } from '../Cards/cardType/Card'
+
+
+import { NavLink } from 'react-router-dom'
+import { AuthContext } from '../context/AuthContext'
+import { PageContext } from '../context/PageContext'
+
+import { useHttp } from '../hooks/http.hook'
+
+function useCardsArr(defaultValue) {
+ const [value, setValue] = React.useState(defaultValue)
+
+ function trySetValue(cardsArr) {
+ if (checkCardsArr(cardsArr) || cardsArr === null) setValue(cardsArr)
+ else console.error('Массив cardsArr не прошел проверку \n', cardsArr)
+ }
+
+ return [value, trySetValue]
+}
+
+function useUpdater() {
+ const [updaterVal, setUpdaterVal] = React.useState(null)
+ const timer = React.useRef()
+ React.useEffect(() => {
+ if (timer.current) clearTimeout(timer.current)
+ timer.current = setTimeout(() => {
+ console.log("Timed update")
+ setUpdaterVal(Date.now())
+ }, 60 * 1000) // обновяем через минуту
+ return () => clearTimeout(timer.current)
+ }, []) // eslint-disable-line react-hooks/exhaustive-deps
+ return [updaterVal]
+}
+
+function CardsPage() {
+
+ const auth = React.useContext(AuthContext)
+ const page = React.useContext(PageContext)
+
+ const { loading, request, error, clearError } = useHttp()
+ const fetchNotes = React.useCallback(async (url = "", method = "GET", body = null, resCallback = () => { }) => {
+ try {
+ const fetched = await request(`/api/notes${url ? ("/" + url) : ""}`, method, body, { Authorization: `Bearer ${auth.token}` })
+ resCallback(tryParce(fetched))
+ } catch (e) { }
+ function tryParce(str) {
+ try {
+ return JSON.parse(str);
+ } catch (e) {
+ return str;
+ }
+ }
+ }, [auth.token, request])
+
+ const [message, setMessage] = React.useState(null)
+
+ React.useEffect(() => {
+ if (error) setMessage([error, false])
+ clearError()
+ }, [error, clearError])
+
+
+
+ const [cardsArr, setCardsArr] = useCardsArr(null)
+ const [editCardId, setEditCardId] = React.useState(null)
+
+ const [updaterVal] = useUpdater()
+
+ const updatingEnable = React.useRef(true)
+
+ React.useEffect(() => {
+ updatingEnable.current = true
+ loadDataFromServer()
+ return () => updatingEnable.current = false
+ }, [auth.isAuthenticated, auth.email, updaterVal]) // eslint-disable-line react-hooks/exhaustive-deps
+
+ React.useEffect(clearOldData, [auth.isAuthenticated]) // eslint-disable-line react-hooks/exhaustive-deps
+
+ ///////////
+ function clearOldData() {
+ //console.log("clearOldData, auth.isAuthenticated:", auth.isAuthenticated)
+ if (!auth.isAuthenticated) setCardsArr(null)
+ }
+ ///////////
+
+ ///////////
+ function loadDataFromServer() {
+ fetchNotes("", "GET", null, setLoadedCards)
+ }
+
+ function setLoadedCards(cards) {
+ if (updatingEnable.current) setCardsArr([...cards])
+ }
+ ///////////
+
+ ///////////
+
+ function loadDataToServer(card = new Card(), target = 'set') {
+ fetchNotes(target, "POST", { card })
+ }
+
+ function removeCard(index) {
+ const toDelete = cardsArr.splice(index, 1)[0]
+ setCardsArr([...cardsArr])
+ loadDataToServer(toDelete, "delete")
+ }
+
+ function addCard(cardData = {}) {
+ const newId = String(auth.email) + String(Date.now()) + String(Math.random())
+ const newCard = new Card({ id: newId, name: cardData.name, color: cardData.color, text: cardData.text })
+ //console.log(newId, newCard.id);
+ setCardsArr(
+ (cardsArr != null) ? cardsArr.concat([newCard]) : [newCard]
+ )
+ loadDataToServer(newCard, "set")
+
+ }
+
+ function changeCardColor(index, color) {
+ cardsArr[index].color = color
+ setCardsArr([...cardsArr])
+ loadDataToServer(cardsArr[index], "set")
+ }
+
+ function editCardContent(index, name, text) {
+ if (cardsArr[index]) {
+ let card = new Card(cardsArr[index])
+ card.name = name
+ card.text = text
+ cardsArr[index] = card
+ }
+ setCardsArr([...cardsArr])
+ loadDataToServer(cardsArr[index], "set")
+ }
+ ///////////
+
+ ///////////
+ function getCardByIndex(index) {
+ return index !== null ? cardsArr[index] : null
+ }
+ function setEditCard(index) {
+ setEditCardId(index)
+ }
+ function unsetEditCard() {
+ setEditCardId(null)
+ }
+ ///////////
+
+ React.useEffect(() => {
+ page.setNav(
+
+
+
+
+ {auth.email}
+
+
+ )
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [auth.email, auth.token])
+
+ return (
+
+
+
+
+
+
+
+ {cardsArr && cardsArr.length ? (
+
+ ) : (loading) ? null : !message ? (
+
+
No cards. You can add a new one!
+
+ ) : (
+
+
Data not loaded
+
{message}
+
+ )}
+
+ {loading &&
+
+
+
+ }
+
+
+
+ );
+}
+
+export default CardsPage
diff --git a/client/src/pages/SharedComponents/Header.css b/client/src/pages/SharedComponents/Header.css
new file mode 100644
index 0000000..a8a7697
--- /dev/null
+++ b/client/src/pages/SharedComponents/Header.css
@@ -0,0 +1,29 @@
+.App-logo {
+ height: 3em;
+ pointer-events: none;
+ user-select: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap");
+
+.brand {
+ font-family: "Roboto", sans-serif;
+ padding-top: 0.2rem !important;
+ pointer-events: none;
+ user-select: none;
+}
diff --git a/client/src/pages/SharedComponents/Header.js b/client/src/pages/SharedComponents/Header.js
new file mode 100644
index 0000000..11e3d8e
--- /dev/null
+++ b/client/src/pages/SharedComponents/Header.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import logo from '../../shared/Logo/logo.svg';
+import './Header.css';
+
+const brandText = "Notes"
+
+function Header(props) {
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default Header;
diff --git a/client/src/routes.js b/client/src/routes.js
new file mode 100644
index 0000000..c38e31f
--- /dev/null
+++ b/client/src/routes.js
@@ -0,0 +1,33 @@
+import React from 'react'
+import { Switch, Route, Redirect } from 'react-router-dom'
+
+import CardsPage from './pages/CardsPage'
+
+import AuthPage from './pages/AuthPage'
+
+export const useRoutes = isAuthenticated => {
+ if (isAuthenticated) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/models/Note.js b/models/Note.js
new file mode 100644
index 0000000..4262ca8
--- /dev/null
+++ b/models/Note.js
@@ -0,0 +1,13 @@
+const { Schema, model, Types } = require('mongoose')
+
+const schema = new Schema({
+ id: { type: String, required: true, unique: true /*, default: String(Date.now) + String(Math.random)*/ },
+ name: { type: String },
+ text: { type: String },
+ color: { type: String },
+ image: { type: String },
+ //date: { type: Date, default: Date.now },
+ owner: { type: Types.ObjectId, ref: 'User', required: true }
+})
+
+module.exports = model('Note', schema)
diff --git a/models/User.js b/models/User.js
index 170b64b..3bc1240 100644
--- a/models/User.js
+++ b/models/User.js
@@ -2,7 +2,8 @@ const {Schema, model, Types} = require('mongoose')
const schema = new Schema({
email: {type: String, required: true, unique: true},
- password: {type: String, required: true}
+ password: {type: String, required: true},
+ notes: [{ type: Types.ObjectId, ref: 'Note' }]
})
module.exports = model('User', schema)
diff --git a/package.json b/package.json
index 115dca4..2f242d5 100644
--- a/package.json
+++ b/package.json
@@ -57,4 +57,4 @@
"cross-env": "^7.0.3",
"nodemon": "^2.0.7"
}
-}
\ No newline at end of file
+}
diff --git a/routes/auth.routes.js b/routes/auth.routes.js
index 98c99c7..380f8c0 100644
--- a/routes/auth.routes.js
+++ b/routes/auth.routes.js
@@ -1,8 +1,8 @@
-const {Router} = require('express')
+const { Router } = require('express')
const bcrypt = require('bcryptjs')
require('dotenv').config()
const jwt = require('jsonwebtoken')
-const {check, validationResult} = require('express-validator')
+const { check, validationResult } = require('express-validator')
const User = require('../models/User')
const router = Router()
@@ -15,35 +15,35 @@ router.post(
.isLength({ min: 6 })
],
async (req, res) => {
- try {
- const errors = validationResult(req)
-
- if (!errors.isEmpty()) {
- return res.status(400).json({
- errors: errors.array(),
- message: 'Некорректный данные при регистрации'
- })
- }
+ try {
+ const errors = validationResult(req)
- const {email, password} = req.body
+ if (!errors.isEmpty()) {
+ return res.status(400).json({
+ errors: errors.array(),
+ message: 'Некорректный данные при регистрации'
+ })
+ }
- const candidate = await User.findOne({ email })
+ const { email, password } = req.body
- if (candidate) {
- return res.status(400).json({ message: 'Такой пользователь уже существует' })
- }
+ const candidate = await User.findOne({ email })
- const hashedPassword = await bcrypt.hash(password, 12)
- const user = new User({ email, password: hashedPassword })
+ if (candidate) {
+ return res.status(400).json({ message: 'Такой пользователь уже существует' })
+ }
- await user.save()
+ const hashedPassword = await bcrypt.hash(password, 12)
+ const user = new User({ email, password: hashedPassword })
- res.status(201).json({ message: 'Пользователь создан' })
+ await user.save()
- } catch (e) {
- res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' })
- }
-})
+ res.status(201).json({ message: 'Пользователь создан' })
+
+ } catch (e) {
+ res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' })
+ }
+ })
// /api/auth/login
router.post(
@@ -53,42 +53,42 @@ router.post(
check('password', 'Введите пароль').exists()
],
async (req, res) => {
- try {
- const errors = validationResult(req)
-
- if (!errors.isEmpty()) {
- return res.status(400).json({
- errors: errors.array(),
- message: 'Некорректный данные при входе в систему'
- })
- }
+ try {
+ const errors = validationResult(req)
- const {email, password} = req.body
+ if (!errors.isEmpty()) {
+ return res.status(400).json({
+ errors: errors.array(),
+ message: 'Некорректный данные при входе в систему'
+ })
+ }
- const user = await User.findOne({ email })
+ const { email, password } = req.body
- if (!user) {
- return res.status(400).json({ message: 'Пользователь не найден' })
- }
+ const user = await User.findOne({ email })
- const isMatch = await bcrypt.compare(password, user.password)
+ if (!user) {
+ return res.status(400).json({ message: 'Пользователь не найден' })
+ }
- if (!isMatch) {
- return res.status(400).json({ message: 'Неверный пароль, попробуйте снова' })
- }
+ const isMatch = await bcrypt.compare(password, user.password)
- const token = jwt.sign(
- { userId: user.id },
- process.env.jwtSecret,
- { expiresIn: '1h' }
- )
+ if (!isMatch) {
+ return res.status(400).json({ message: 'Неверный пароль, попробуйте снова' })
+ }
- res.json({ token, userId: user.id })
+ const token = jwt.sign(
+ { userId: user.id },
+ process.env.jwtSecret,
+ { expiresIn: '1h' }
+ )
- } catch (e) {
- res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' })
- }
-})
+ res.json({ token, userId: user.id, email: user.email })
+
+ } catch (e) {
+ res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' })
+ }
+ })
module.exports = router
diff --git a/routes/notes.routes.js b/routes/notes.routes.js
new file mode 100644
index 0000000..da913ac
--- /dev/null
+++ b/routes/notes.routes.js
@@ -0,0 +1,95 @@
+const { Router } = require('express')
+
+const Note = require('../models/Note')
+const auth = require('../middleware/auth.middleware')
+const router = Router()
+
+const { checkCard } = require('../validation/CardCheck')
+
+
+router.post('/set', auth, async (req, res) => {
+ try {
+ const card = tryParce(req.body.card)
+ if (checkCard(card)) {
+ postNote(card)
+ res.status(201).json({ card: card })
+ } else {
+ res.status(500).json({ message: 'Неверный формат данных заметки' })
+ }
+ } catch (e) {
+ res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' })
+ }
+
+ async function postNote(noteToSave) {
+
+ noteToSave.owner = req.user.userId
+
+ const existing = await Note.findOne({ id: noteToSave.id })
+
+ if (existing) {
+ //console.log("EXITING");
+ existing.overwrite(noteToSave)
+ existing.save()
+ } else {
+ //console.log("NON EXITING")
+ const note = new Note(noteToSave)
+ await note.save()
+ }
+
+ }
+})
+
+
+
+router.post('/delete', auth, async (req, res) => {
+ try {
+
+ const card = tryParce(req.body.card)
+ if (checkCard(card)) {
+ deleteNote(card)
+ res.status(201).json({ card: card })
+ } else {
+ res.status(500).json({ message: 'Неверный формат данных заметки' })
+ }
+ } catch (e) {
+ res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' })
+ }
+
+ async function deleteNote(noteToSave) {
+ noteToSave.owner = req.user.userId
+
+ const existing = await Note.findOne({ id: noteToSave.id })
+
+ if (existing) {
+ //console.log("EXITING");
+
+ existing.remove()
+
+ } else res.status(500).json({ message: 'уже удален' })
+ }
+})
+
+
+
+router.get('/', auth, async (req, res) => {
+ try {
+ //console.log(req.user.userId);
+ const notes = await Note.find({ owner: req.user.userId })
+ //console.log(notes);
+ res.json(notes)
+ } catch (e) {
+ //console.log("\n\n\n GET");
+ //console.log(e);
+ res.status(500).json({ message: '[GET] Что-то пошло не так, попробуйте снова' })
+ }
+})
+
+function tryParce(str) {
+ try {
+ return JSON.parse(str);
+ } catch (e) {
+ return str;
+ }
+}
+
+module.exports = router
\ No newline at end of file
diff --git a/routes/phpserver.routes.js b/routes/phpserver.routes.js
deleted file mode 100644
index f9b6b81..0000000
--- a/routes/phpserver.routes.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const { Router } = require('express')
-//const auth = require('../middleware/auth.middleware')
-const router = Router()
-
-//temporary backend url
-const phpBaseUrl = 'https://php-server-notes.herokuapp.com/'
-
-//auth not used
-router.post('/', /*auth,*/ async (req, res) => {
- try {
- //console.log("backend redirect", req.url)
- res.redirect(307, phpBaseUrl)
- } catch (e) {
- res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' })
- }
-})
-
-module.exports = router
diff --git a/validation/CardCheck.js b/validation/CardCheck.js
new file mode 100644
index 0000000..4e60bb1
--- /dev/null
+++ b/validation/CardCheck.js
@@ -0,0 +1,10 @@
+function checkCard(card) {
+ return (
+ typeof card.id === "string" &&
+ typeof card.name === "string" &&
+ typeof card.color === "string" &&
+ typeof card.text === "string"
+ )
+}
+
+module.exports = { checkCard }
\ No newline at end of file