diff --git a/typescript-practice/.vscode/launch.json b/typescript-practice/.vscode/launch.json new file mode 100644 index 0000000..ac819dd --- /dev/null +++ b/typescript-practice/.vscode/launch.json @@ -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}/*" + } + } + ] +} diff --git a/typescript-practice/package-lock.json b/typescript-practice/package-lock.json index 30a70f8..eaf61b4 100644 --- a/typescript-practice/package-lock.json +++ b/typescript-practice/package-lock.json @@ -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", diff --git a/typescript-practice/package.json b/typescript-practice/package.json index fcbe694..6a0a413 100644 --- a/typescript-practice/package.json +++ b/typescript-practice/package.json @@ -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" } } diff --git a/typescript-practice/src/pages/register.html b/typescript-practice/src/pages/register.html index 62f6b0d..c608c00 100644 --- a/typescript-practice/src/pages/register.html +++ b/typescript-practice/src/pages/register.html @@ -57,5 +57,6 @@ + diff --git a/typescript-practice/src/styles/components/_icons.scss b/typescript-practice/src/styles/components/_icons.scss index 8455f60..fe28d17 100644 --- a/typescript-practice/src/styles/components/_icons.scss +++ b/typescript-practice/src/styles/components/_icons.scss @@ -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); + } } } diff --git a/typescript-practice/src/ts/app.ts b/typescript-practice/src/ts/app.ts new file mode 100644 index 0000000..4c65fe6 --- /dev/null +++ b/typescript-practice/src/ts/app.ts @@ -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(); + } +} diff --git a/typescript-practice/src/ts/constants/config.ts b/typescript-practice/src/ts/constants/config.ts index 9b88deb..e1d583b 100644 --- a/typescript-practice/src/ts/constants/config.ts +++ b/typescript-practice/src/ts/constants/config.ts @@ -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', } diff --git a/typescript-practice/src/ts/controllers/index.ts b/typescript-practice/src/ts/controllers/index.ts new file mode 100644 index 0000000..390bfc6 --- /dev/null +++ b/typescript-practice/src/ts/controllers/index.ts @@ -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); + } +} diff --git a/typescript-practice/src/ts/controllers/registerController.ts b/typescript-practice/src/ts/controllers/registerController.ts new file mode 100644 index 0000000..a8b9a27 --- /dev/null +++ b/typescript-practice/src/ts/controllers/registerController.ts @@ -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 { + return this.service.userService.isValidUser(email); + } + + handlerSaveUser(user: User): Promise { + return this.service.userService.saveUser(user); + } + + handlerGetInfoUserLogin(): Promise { + 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(); + } + } +} diff --git a/typescript-practice/src/ts/global/types.ts b/typescript-practice/src/ts/global/types.ts index fb1c264..1094600 100644 --- a/typescript-practice/src/ts/global/types.ts +++ b/typescript-practice/src/ts/global/types.ts @@ -13,3 +13,8 @@ export class DataObject { this.data = dataObject.data as T; } } + +export type TError = { + title: string; + message: string; +}; diff --git a/typescript-practice/src/ts/index.ts b/typescript-practice/src/ts/index.ts index 2edaa02..851ad8e 100644 --- a/typescript-practice/src/ts/index.ts +++ b/typescript-practice/src/ts/index.ts @@ -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(); +}); diff --git a/typescript-practice/src/ts/models/user.ts b/typescript-practice/src/ts/models/user.ts index e5fb6e2..808a41a 100644 --- a/typescript-practice/src/ts/models/user.ts +++ b/typescript-practice/src/ts/models/user.ts @@ -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; + } } diff --git a/typescript-practice/src/ts/services/commonService.ts b/typescript-practice/src/ts/services/commonService.ts index 579bb73..e01591c 100644 --- a/typescript-practice/src/ts/services/commonService.ts +++ b/typescript-practice/src/ts/services/commonService.ts @@ -7,8 +7,13 @@ import { DataObject } from '../global/types'; import FirebaseService from './firebaseService'; export default class CommonService { - public defaultPath: string = '/'; - private firebaseService = FirebaseService; + public defaultPath: string; + + public firebaseService = FirebaseService; + + constructor(defaultPath?: string) { + this.defaultPath = defaultPath ? defaultPath : '/'; + } /** * Connect to Firebase Database diff --git a/typescript-practice/src/ts/services/firebaseService.ts b/typescript-practice/src/ts/services/firebaseService.ts index b4290c9..427ee41 100644 --- a/typescript-practice/src/ts/services/firebaseService.ts +++ b/typescript-practice/src/ts/services/firebaseService.ts @@ -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) => { diff --git a/typescript-practice/src/ts/services/index.ts b/typescript-practice/src/ts/services/index.ts index a5d1d4c..a9a3355 100644 --- a/typescript-practice/src/ts/services/index.ts +++ b/typescript-practice/src/ts/services/index.ts @@ -1 +1,9 @@ -export default class Service {} +import UserService from './userService'; + +export default class Service { + public userService: UserService; + + constructor() { + this.userService = new UserService(); + } +} diff --git a/typescript-practice/src/ts/services/userService.ts b/typescript-practice/src/ts/services/userService.ts index 9809860..1abf86f 100644 --- a/typescript-practice/src/ts/services/userService.ts +++ b/typescript-practice/src/ts/services/userService.ts @@ -8,15 +8,15 @@ export default class UserService { private _commonService: CommonService; constructor() { - this._commonService = new CommonService(); + this._commonService = new CommonService('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 { + await this._commonService.save(user); } /** diff --git a/typescript-practice/src/ts/views/authenticationView.ts b/typescript-practice/src/ts/views/authenticationView.ts new file mode 100644 index 0000000..a045dcc --- /dev/null +++ b/typescript-practice/src/ts/views/authenticationView.ts @@ -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) => `
  • ${message}
  • `) + .join('\n'); + + const markup = ` +
      + ${messageItemMarkup} +
    + `; + + const titleEl = document.querySelector('.form__title'); + + if (titleEl) titleEl.insertAdjacentHTML('afterend', markup); + } + } +} diff --git a/typescript-practice/src/ts/views/commonView.ts b/typescript-practice/src/ts/views/commonView.ts new file mode 100644 index 0000000..4791241 --- /dev/null +++ b/typescript-practice/src/ts/views/commonView.ts @@ -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 = ` + +
    +
    +

    +

    + +
    +
    + `; + + 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 = ``; + + const body = document.querySelector('body'); + if (body) body.insertAdjacentHTML('afterbegin', markup); + + // Init element + this.spinner = document.querySelector('.loader'); + } +} diff --git a/typescript-practice/src/ts/views/index.ts b/typescript-practice/src/ts/views/index.ts new file mode 100644 index 0000000..8a45c8d --- /dev/null +++ b/typescript-practice/src/ts/views/index.ts @@ -0,0 +1,9 @@ +import RegisterView from './registerView'; + +export default class View { + public registerView: RegisterView; + + constructor() { + this.registerView = new RegisterView(); + } +} diff --git a/typescript-practice/src/ts/views/registerView.ts b/typescript-practice/src/ts/views/registerView.ts new file mode 100644 index 0000000..c02e336 --- /dev/null +++ b/typescript-practice/src/ts/views/registerView.ts @@ -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) { + 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, + saveUser: (user: User) => Promise, + ) { + if (this.formEl) { + this.formEl.addEventListener('submit', (e) => { + e.preventDefault(); + this.clearErrorMessage(); + this.submitForm(checkExistUser, saveUser); + }); + } + } + + async submitForm( + checkExistUser: (email: string) => Promise, + saveUser: (user: User) => Promise, + ) { + 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(); + } +} diff --git a/typescript-practice/tsconfig.json b/typescript-practice/tsconfig.json index 4ddeb29..f6aca66 100644 --- a/typescript-practice/tsconfig.json +++ b/typescript-practice/tsconfig.json @@ -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", diff --git a/typescript-practice/vercel.json b/typescript-practice/vercel.json new file mode 100644 index 0000000..bd6fd8f --- /dev/null +++ b/typescript-practice/vercel.json @@ -0,0 +1,3 @@ +{ + "cleanUrls": true +}