Merge pull request #6 from Nez27/feat/add-register-method

Add register method
This commit is contained in:
Loi Phan
2023-09-29 16:56:19 +07:00
committed by GitHub
22 changed files with 993 additions and 52 deletions
+19
View File
@@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "msedge",
"request": "launch",
"name": "Launch Edge against localhost",
"url": "http://localhost:1234",
"webRoot": "${workspaceFolder}",
"breakOnLoad": true,
"sourceMapPathOverrides": {
"/__parcel_source_root/*": "${webRoot}/*"
}
}
]
}
+310
View File
@@ -9,6 +9,8 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@types/core-js": "^2.5.6",
"@types/regenerator-runtime": "^0.13.2",
"firebase": "^10.4.0",
"parcel": "^2.9.3"
},
@@ -23,6 +25,7 @@
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^5.0.0",
"prettier": "^3.0.3",
"process": "^0.11.10",
"typescript": "^5.2.2"
}
},
@@ -2731,6 +2734,11 @@
"node": ">=10.13.0"
}
},
"node_modules/@types/core-js": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@types/core-js/-/core-js-2.5.6.tgz",
"integrity": "sha512-zLzoC7avO4EYUUYCSzDaahSP1QJEpZQcPxqs91mPeFdh2NS4hQBcnRoEc9RuXfJ8cdN/KXUWukMmZGcKaWeOvw=="
},
"node_modules/@types/json-schema": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
@@ -2748,6 +2756,11 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.1.tgz",
"integrity": "sha512-LT+OIXpp2kj4E2S/p91BMe+VgGX2+lfO+XTpfXhh+bCk2LkQtHZSub8ewFBMGP5ClysPjTDFa4sMI8Q3n4T0wg=="
},
"node_modules/@types/regenerator-runtime": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/@types/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
"integrity": "sha512-SIYCe4xgq4FSs00vENNHC+L1jxRdN4IKV2abqTu2kb2WkXlPBOml7NK2eCcYdUbQtD8cOWTcBUiYstoiaSNawA=="
},
"node_modules/@types/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
@@ -3168,6 +3181,15 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
"dev": true,
"engines": {
"node": ">=0.6"
}
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -3246,6 +3268,21 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/bundle-name": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
"integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
"dev": true,
"dependencies": {
"run-applescript": "^5.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -4458,6 +4495,12 @@
"is-callable": "^1.1.3"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -4914,6 +4957,18 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
"node_modules/is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"dev": true,
"dependencies": {
"has-bigints": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -4926,6 +4981,22 @@
"node": ">=8"
}
},
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -5050,6 +5121,21 @@
"node": ">=0.12.0"
}
},
"node_modules/is-number-object": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
"dev": true,
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
@@ -5087,6 +5173,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-string": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@@ -5171,6 +5269,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"dev": true
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5531,6 +5641,12 @@
"optional": true,
"peer": true
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -5552,6 +5668,18 @@
"node": ">=8.6"
}
},
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5673,6 +5801,33 @@
"node": ">=0.10.0"
}
},
"node_modules/npm-run-path": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
"integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
"dev": true,
"dependencies": {
"path-key": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npm-run-path/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -5785,6 +5940,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/onetime": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
"dev": true,
"dependencies": {
"mimic-fn": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/open": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
@@ -5923,6 +6102,15 @@
"node": ">=8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -6048,6 +6236,15 @@
"node": ">=6.0.0"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"dev": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/protobufjs": {
"version": "7.2.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz",
@@ -6205,6 +6402,110 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/run-applescript": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
"integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
"dev": true,
"dependencies": {
"execa": "^5.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-applescript/node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
"human-signals": "^2.1.0",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.1",
"onetime": "^5.1.2",
"signal-exit": "^3.0.3",
"strip-final-newline": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/run-applescript/node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true,
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/run-applescript/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-applescript/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/run-applescript/node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dev": true,
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/run-applescript/node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"dependencies": {
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-applescript/node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6781,6 +7082,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+3
View File
@@ -10,6 +10,8 @@
"author": "",
"license": "ISC",
"dependencies": {
"@types/core-js": "^2.5.6",
"@types/regenerator-runtime": "^0.13.2",
"firebase": "^10.4.0",
"parcel": "^2.9.3"
},
@@ -24,6 +26,7 @@
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^5.0.0",
"prettier": "^3.0.3",
"process": "^0.11.10",
"typescript": "^5.2.2"
}
}
@@ -57,5 +57,6 @@
</div>
</div>
</main>
<script type="module" src="../ts/index.ts"></script>
</body>
</html>
@@ -13,31 +13,15 @@
display: block;
position: absolute;
}
}
.check:before {
top: 16px;
left: 15px;
margin: -5px 0 0 -6px;
height: 10px;
width: 20px;
border: solid #39ca74;
border-width: 0 0 3px 3px;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.error {
&:before {
top: 6px;
left: 13px;
&.success:before {
top: 16px;
left: 15px;
margin: -5px 0 0 -6px;
height: 10px;
width: 10px;
border: solid #c0392b;
border-width: 0 0 4px 4px;
width: 20px;
border: solid #39ca74;
border-width: 0 0 3px 3px;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
@@ -45,17 +29,33 @@
transform: rotate(-45deg);
}
&:after {
top: 21px;
left: 13px;
height: 10px;
width: 10px;
border: solid #c0392b;
border-width: 0 0 4px 4px;
-webkit-transform: rotate(135deg);
-moz-transform: rotate(135deg);
-ms-transform: rotate(135deg);
-o-transform: rotate(135deg);
transform: rotate(135deg);
&.error {
&:before {
top: 6px;
left: 13px;
height: 10px;
width: 10px;
border: solid #c0392b;
border-width: 0 0 4px 4px;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
&:after {
top: 21px;
left: 13px;
height: 10px;
width: 10px;
border: solid #c0392b;
border-width: 0 0 4px 4px;
-webkit-transform: rotate(135deg);
-moz-transform: rotate(135deg);
-ms-transform: rotate(135deg);
-o-transform: rotate(135deg);
transform: rotate(135deg);
}
}
}
+15
View File
@@ -0,0 +1,15 @@
import Controller from './controllers/index';
import Service from './services/index';
import View from './views/index';
export default class App {
private controller: Controller;
constructor() {
this.controller = new Controller(new Service(), new View());
}
start() {
this.controller.registerController.init();
}
}
@@ -11,12 +11,12 @@ export const LOCAL_STORAGE = {
ACCESS_TOKEN: 'accessToken',
};
export enum TYPE_TOAST {
export enum TypeToast {
success = 'success',
error = 'error',
}
export enum MARK_ICON {
success = 'check',
export enum MarkIcon {
success = 'success',
error = 'error',
}
@@ -0,0 +1,14 @@
import Service from 'services';
import RegisterController from './registerController';
import View from 'views';
export default class Controller {
public registerController: RegisterController;
constructor(
public service: Service,
public view: View,
) {
this.registerController = new RegisterController(service, view);
}
}
@@ -0,0 +1,39 @@
import User from 'models/user';
import Service from 'services';
import View from 'views';
import RegisterView from 'views/registerView';
export default class RegisterController {
public registerView: RegisterView;
constructor(
public service: Service,
public view: View,
) {
this.registerView = view.registerView;
this.service = service;
}
handlerCheckUserValid(email: string): Promise<boolean> {
return this.service.userService.isValidUser(email);
}
handlerSaveUser(user: User): Promise<void> {
return this.service.userService.saveUser(user);
}
handlerGetInfoUserLogin(): Promise<User | null> {
return this.service.userService.getInfoUserLogin();
}
init() {
if (this.registerView) {
this.registerView.loadPage(this.handlerGetInfoUserLogin.bind(this));
this.registerView.addHandlerForm(
this.handlerCheckUserValid.bind(this),
this.handlerSaveUser.bind(this),
);
this.registerView.addHandlerInputFormChange();
}
}
}
@@ -13,3 +13,8 @@ export class DataObject<T> {
this.data = dataObject.data as T;
}
}
export type TError = {
title: string;
message: string;
};
+8 -1
View File
@@ -1 +1,8 @@
console.log('Test');
import App from './app';
// Sure that scripts called after DOM loaded
document.addEventListener('DOMContentLoaded', () => {
const myApp = new App();
myApp.start();
});
+12 -4
View File
@@ -1,7 +1,7 @@
import { createIdUser } from '../helpers/data';
export default class User {
private id: number;
private readonly id: number;
private email: string;
@@ -9,11 +9,11 @@ export default class User {
private accessToken: string;
constructor(email: string, password: string, accessToken: string) {
constructor(email: string, password: string, accessToken?: string) {
this.id = createIdUser();
this.email = email;
this.password = password;
this.accessToken = accessToken;
this.password = password || '';
this.accessToken = accessToken || '';
}
get getPassword() {
@@ -27,4 +27,12 @@ export default class User {
get getAccessToken() {
return this.accessToken;
}
get getEmail() {
return this.email;
}
get getId() {
return this.id;
}
}
@@ -7,8 +7,13 @@ import { DataObject } from '../global/types';
import FirebaseService from './firebaseService';
export default class CommonService<T> {
public defaultPath: string = '/';
private firebaseService = FirebaseService;
public defaultPath: string;
public firebaseService = FirebaseService;
constructor(defaultPath?: string) {
this.defaultPath = defaultPath ? defaultPath : '/';
}
/**
* Connect to Firebase Database
@@ -13,6 +13,7 @@ import { DATABASE_URL } from '../constants/config';
class FirebaseService {
private app: FirebaseApp;
private db: Database;
constructor() {
@@ -119,7 +120,7 @@ class FirebaseService {
onValue(
ref(this.db, path),
(snapshot) => {
let listData: object[] = [];
const listData: object[] = [];
// snapshot is a type of data by Firebase define
snapshot.forEach((childSnapshot) => {
+9 -1
View File
@@ -1 +1,9 @@
export default class Service {}
import UserService from './userService';
export default class Service {
public userService: UserService;
constructor() {
this.userService = new UserService();
}
}
@@ -8,15 +8,15 @@ export default class UserService {
private _commonService: CommonService<User>;
constructor() {
this._commonService = new CommonService<User>();
this._commonService = new CommonService<User>('users/');
}
/**
* Save user into database
* @param {Object} user The user object need to be saved into databae
* @param {Object} user The user object need to be saved into database
*/
saveUser(user: User): void {
this._commonService.save(user);
async saveUser(user: User): Promise<void> {
await this._commonService.save(user);
}
/**
@@ -0,0 +1,159 @@
import CommonView from './commonView';
import { ERROR_MESSAGE_DEFAULT } from '../constants/messages/dialog';
import {
INVALID_EMAIL_FORMAT,
PASSWORD_NOT_MATCH,
PASSWORD_NOT_STRONG,
} from 'constants/messages/form';
import {
compare2Password,
isValidPassword,
isValidateEmail,
renderRequiredText,
} from '../helpers/validatorForm';
export default class AuthenticationView extends CommonView {
public formEl: HTMLBodyElement | null;
public emailEl: HTMLBodyElement | null;
public messageDefault: string[];
public inputPasswordEl: HTMLBodyElement | null;
public inputPasswordConfirmEl: HTMLBodyElement | null;
public listError: string[];
constructor() {
super();
this.formEl = document.querySelector('.form');
this.emailEl = document.querySelector("[name='email']");
this.messageDefault = ERROR_MESSAGE_DEFAULT;
this.inputPasswordEl = document.querySelector('input[name="password"]');
this.inputPasswordConfirmEl = document.querySelector(
'input[name="password_confirm"]',
);
this.listError = [];
}
// eslint-disable-next-line class-methods-use-this
validateEmail(email: string): boolean {
if (email) {
if (!isValidateEmail(email)) {
this.listError.push(INVALID_EMAIL_FORMAT);
return false;
}
return true;
}
if (this.emailEl) renderRequiredText('email', this.emailEl);
return false;
}
validatePassword(password: string): boolean {
if (password) {
if (!isValidPassword(password)) {
this.listError.push(PASSWORD_NOT_STRONG);
return false;
}
return true;
}
if (this.inputPasswordEl)
renderRequiredText('password', this.inputPasswordEl);
return false;
}
validatePasswordConfirm(password: string, passwordConfirm: string) {
if (passwordConfirm) {
if (!compare2Password(password, passwordConfirm)) {
this.listError.push(PASSWORD_NOT_MATCH);
return false;
}
return true;
}
if (this.inputPasswordConfirmEl)
renderRequiredText('confirm password', this.inputPasswordConfirmEl);
return false;
}
/**
* Clear error message at form
*/
clearErrorMessage() {
// Reassign again to check error message element haved on page or not
const errorMessageEl = document.querySelector('.form__error-message');
const errorTextEl = document.querySelectorAll('.error-text');
// If have error message on page, remove it with style error input password
if (errorMessageEl || errorTextEl.length > 0) {
if (errorMessageEl) errorMessageEl.remove();
if (errorTextEl) {
errorTextEl.forEach((item) => {
item.remove();
});
}
// Use for 2 form
if (this.emailEl && this.inputPasswordEl) {
this.emailEl.classList.remove('error-input');
this.inputPasswordEl.classList.remove('error-input');
}
// Only register form required
if (this.inputPasswordConfirmEl)
this.inputPasswordConfirmEl.classList.remove('error-input');
}
}
/**
* Add event listener for input field at form
*/
addHandlerInputFormChange(): void {
if (this.formEl)
this.formEl.addEventListener('input', () => {
this.clearErrorMessage();
});
}
/**
* Show error message with error style input password.
* @param {string} message The error message you want show in form.
*/
showError(message: string[]): void {
this.renderError(message);
}
/**
* Show error message in form
* @param {string} message The message will show in form
*/
renderError(messages = this.messageDefault): void {
if (messages.length > 0) {
const messageItemMarkup = messages
.map((message) => `<li>${message}</li>`)
.join('\n');
const markup = `
<ul class="form__error-message">
${messageItemMarkup}
</ul>
`;
const titleEl = document.querySelector('.form__title');
if (titleEl) titleEl.insertAdjacentHTML('afterend', markup);
}
}
}
@@ -0,0 +1,160 @@
import { MarkIcon, TypeToast } from '../constants/config';
export default class CommonView {
public toastDialog: HTMLDialogElement | null = null;
public toastIcon: HTMLBodyElement | null = null;
public toastBtn: HTMLBodyElement | null = null;
public toastTitle: HTMLBodyElement | null = null;
public toastContent: HTMLBodyElement | null = null;
public spinner: HTMLBodyElement | null = null;
constructor() {
this.toastDialog = document.querySelector('.dialog');
this.spinner = null;
}
/**
* Implement toast in site
*/
initToast(): void {
const markup = `
<dialog class="dialog">
<div class="toast">
<div class="mark"></div>
<h2 class="toast__title"></h2>
<p class="toast__message"></p>
<button class="toast__redirect-btn">OK</button>
</div>
</dialog>
`;
const body = document.querySelector('body');
if (body) body.insertAdjacentHTML('afterbegin', markup);
this.initToastEl();
}
initToastEl() {
this.toastDialog = document.querySelector('.dialog');
this.toastIcon = this.toastDialog
? this.toastDialog.querySelector('.mark')
: null;
this.toastBtn = this.toastDialog
? this.toastDialog.querySelector('.toast__redirect-btn')
: null;
this.toastTitle = this.toastDialog
? this.toastDialog.querySelector('.toast__title')
: null;
this.toastContent = this.toastDialog
? this.toastDialog.querySelector('.toast__message')
: null;
}
/**
* Show or hide loader screen
*/
toggleLoaderSpinner(): void {
if (this.spinner) this.spinner.classList.toggle('hidden');
}
/**
* Add toast content
* @param {TYPE_TOAST} typeToast Type of the toast
* @param {string} title Title of toast
* @param {string} content Content of toast
* @param {string} btnContent Content of button
*/
initToastContent(
typeToast: string,
title: string,
content: string,
btnContent: string,
): void {
// Remove old typeToast class if haved
Object.keys(TypeToast).forEach((key) => {
CommonView.removeClassElement(
TypeToast[key as TypeToast],
this.toastDialog as HTMLBodyElement | null,
);
});
// Remove old icon toast if haved
Object.keys(MarkIcon).forEach((key) => {
CommonView.removeClassElement(MarkIcon[key as MarkIcon], this.toastIcon);
});
// Init content toast
if (
this.toastDialog &&
this.toastIcon &&
this.toastTitle &&
this.toastContent &&
this.toastBtn
) {
this.toastDialog.classList.add(
typeToast === TypeToast.success ? TypeToast.success : TypeToast.error,
);
this.toastIcon.classList.add(
typeToast === TypeToast.success ? MarkIcon.success : MarkIcon.error,
);
this.toastTitle.textContent = title;
this.toastContent.textContent = content;
this.toastBtn.textContent = btnContent;
}
}
static removeClassElement(classEl: string, el: HTMLBodyElement | null): void {
if (el && el.classList.contains(classEl)) {
el.classList.remove(classEl);
}
}
/**
* Add event listener for toast
*/
handleEventToast(): void {
if (this.toastBtn) {
this.toastBtn.addEventListener('click', () => {
if (this.toastDialog) this.toastDialog.close();
});
}
// Add event close dialog when click outside
if (this.toastDialog) {
this.toastDialog.addEventListener('click', (e) => {
if (this.toastDialog) {
const dialogDimensions = this.toastDialog.getBoundingClientRect();
if (
e.clientX < dialogDimensions.left ||
e.clientX > dialogDimensions.right ||
e.clientY < dialogDimensions.top ||
e.clientY > dialogDimensions.bottom
) {
this.toastDialog.close();
}
}
});
}
}
/**
* Implement loader screen
*/
initLoader() {
const markup = `<div class="loader hidden"></div>`;
const body = document.querySelector('body');
if (body) body.insertAdjacentHTML('afterbegin', markup);
// Init element
this.spinner = document.querySelector('.loader');
}
}
@@ -0,0 +1,9 @@
import RegisterView from './registerView';
export default class View {
public registerView: RegisterView;
constructor() {
this.registerView = new RegisterView();
}
}
@@ -0,0 +1,175 @@
import { TypeToast, BTN_CONTENT } from '../constants/config';
import AuthenticationView from './authenticationView';
import User from '../models/user';
import { redirectToLoginPage } from '../helpers/redirect';
import {
DEFAULT_MESSAGE,
DEFAULT_TITLE_ERROR_TOAST,
REGISTER_SUCCESS,
USER_EXIST_ERROR,
} from 'constants/messages/dialog';
import { TError } from 'global/types';
export default class RegisterView extends AuthenticationView {
constructor() {
super();
this.initToast();
this.initLoader();
this.handleEventToast();
this.toastBtn = document.querySelector('.toast__redirect-btn');
}
async loadPage(getInfoUserLogin: () => Promise<User | null>) {
this.toggleLoaderSpinner();
const user = await getInfoUserLogin();
if (user) {
window.location.replace('/');
}
this.toggleLoaderSpinner();
}
/**
* Get data from user input
* @returns {Object || null} Return object or null
*/
validateForm(): User | null {
const form = document.getElementById('registerForm') as HTMLFormElement;
const formData = new FormData(form);
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const passwordConfirm = formData.get('password_confirm') as string;
// Validate user input
this.listError = []; // Reset list error
const emailValid = this.validateEmail(email);
const passwordValid = this.validatePassword(password);
const passwordConfirmValid = this.validatePasswordConfirm(
password,
passwordConfirm,
);
// Show error style
if (this.emailEl && this.inputPasswordEl && this.inputPasswordConfirmEl) {
this.emailEl.classList.toggle('error-input', !emailValid);
this.inputPasswordEl.classList.toggle('error-input', !passwordValid);
this.inputPasswordConfirmEl.classList.toggle(
'error-input',
!passwordConfirmValid,
);
}
if (emailValid && passwordValid && passwordConfirmValid) {
return new User(email, password);
}
this.showError(this.listError);
return null;
}
/**
* Implement register success toast in site
*/
showRegisterSuccessToast() {
const typeToast = TypeToast.success;
const title = REGISTER_SUCCESS;
const content = DEFAULT_MESSAGE;
const btnContent = BTN_CONTENT.OK;
this.initToastContent(typeToast, title, content, btnContent);
// Show toast
if (this.toastDialog) this.toastDialog.showModal();
// Add event for toast button
if (this.toastBtn)
this.toastBtn.addEventListener('click', redirectToLoginPage);
}
/**
* Implement error toast in site
* @param {string} content The content will show in error toast
*/
initErrorToast(error: TError | string): void {
const title =
typeof error === 'object' && error.title
? error.title
: DEFAULT_TITLE_ERROR_TOAST;
const content =
typeof error === 'object' && error.message
? error.message
: (error as string);
this.initToastContent(TypeToast.error, title, content, BTN_CONTENT.OK);
if (this.toastDialog && this.toastBtn) {
// Show toast
this.toastDialog.showModal();
// Remove event for toast button
this.toastBtn.removeEventListener('click', redirectToLoginPage);
}
}
/**
* Add event listener for form input
* @param {Function} handler The function need to be set event
*/
addHandlerForm(
checkExistUser: (email: string) => Promise<boolean>,
saveUser: (user: User) => Promise<void>,
) {
if (this.formEl) {
this.formEl.addEventListener('submit', (e) => {
e.preventDefault();
this.clearErrorMessage();
this.submitForm(checkExistUser, saveUser);
});
}
}
async submitForm(
checkExistUser: (email: string) => Promise<boolean>,
saveUser: (user: User) => Promise<void>,
) {
try {
// Load spinner
this.toggleLoaderSpinner();
// Get validate form
const user = this.validateForm();
// Save user
if (user) {
// Check user exist
const userExist = await checkExistUser(user.getEmail);
if (userExist) {
throw Error(USER_EXIST_ERROR);
} else {
await saveUser(user);
// Show toast success
this.showRegisterSuccessToast();
// Clear form
const formEl = document.getElementById(
'registerForm',
) as HTMLFormElement;
if (formEl) formEl.reset();
}
}
} catch (error) {
// Show toast error
this.initErrorToast(error as string | TError);
}
// Close spinner
this.toggleLoaderSpinner();
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"target": "ES2015" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"dom",
"ES2017",
+3
View File
@@ -0,0 +1,3 @@
{
"cleanUrls": true
}