diff --git a/package-lock.json b/package-lock.json index df1e616..db6a973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.2.7", "antd": "^5.21.1", + "nodemon": "^3.1.7", "normalize.css": "^8.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -28,6 +29,7 @@ "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", "prettier": "^3.3.3", + "ts-node": "^10.9.2", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", "vite": "^5.4.1" @@ -142,6 +144,19 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -731,6 +746,34 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1401,6 +1444,34 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1415,6 +1486,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.2.tgz", + "integrity": "sha512-866lXSrpGpgyHBZUa2m9YNWqHDjjM0aBTJlNtYaGEw4rqY/dcD7deRVTbBBAJelfA7oaGDbNftXF/TL/A6RgoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -1720,6 +1802,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1824,6 +1919,26 @@ "url": "https://opencollective.com/ant-design" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1994,14 +2109,24 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2012,7 +2137,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2077,6 +2201,42 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -2113,7 +2273,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/copy-to-clipboard": { @@ -2125,6 +2284,13 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2230,7 +2396,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2287,6 +2452,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -2819,7 +2994,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -2880,7 +3054,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3129,6 +3302,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, "node_modules/immer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", @@ -3227,6 +3406,18 @@ "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", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "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", @@ -3309,7 +3500,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3348,7 +3538,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -3387,7 +3576,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -3708,6 +3896,13 @@ "loose-envify": "cli.js" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3736,7 +3931,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3749,7 +3943,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3777,6 +3970,64 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize.css": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", @@ -3987,7 +4238,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4079,6 +4329,12 @@ "react-is": "^16.13.1" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4809,6 +5065,18 @@ "react-dom": ">=16.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -5048,7 +5316,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5139,6 +5406,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5390,7 +5669,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -5405,6 +5683,15 @@ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", "license": "MIT" }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5418,6 +5705,50 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -5568,6 +5899,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5587,6 +5932,13 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.7", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", @@ -5756,6 +6108,16 @@ "node": ">=0.10.0" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 435ab5a..e4374f8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.2.7", "antd": "^5.21.1", + "nodemon": "^3.1.7", "normalize.css": "^8.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -31,6 +32,7 @@ "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", "prettier": "^3.3.3", + "ts-node": "^10.9.2", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", "vite": "^5.4.1" diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000..76cccd6 Binary files /dev/null and b/src/assets/images/logo.png differ diff --git a/src/assets/images/logo_kakao.png b/src/assets/images/logo_kakao.png new file mode 100644 index 0000000..6fec281 Binary files /dev/null and b/src/assets/images/logo_kakao.png differ diff --git a/src/components/LoginBox.tsx b/src/components/LoginBox.tsx new file mode 100644 index 0000000..8b1b95b --- /dev/null +++ b/src/components/LoginBox.tsx @@ -0,0 +1,221 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { Input, Button } from "antd"; +import { useDispatch, useSelector } from "react-redux"; +import { LOGIN } from "../features/redux/store"; + +// // store의 isLogin 값에 접근 +// const selectIsLogin = (state: any) => state.user.isLogin; + +//UserData 정의 +interface UserData { + email: string; + password: string; +} + +//props 정의 +interface PropsData { + userData: UserData; +} + +// 로그인 박스 스타일 지정 +const BoxStyle = styled.div` + width: 500px; + height: 600px; + background-color: #ffffff; + padding: 30px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + border: 1px solid #efefef; + + form { + width: 100%; + } +`; + +// styled component : 로고 +const LogoStyle = styled.img` + width: 250px; + height: 50px; + margin-bottom: 50px; +`; + +// styled component : 입력창 +const InputStyle = styled(Input)` + padding: 15px; +`; + +// styled component : 에러 메시지 +const ErrorMessage = styled.div` + color: red; + margin: 5px 0; + height: 37px; + overflow: hidden; + min-height: 20px; + font-size: 15px; +`; + +// styled component : 로그인 버튼 +const LoginButton = styled(Button)` + padding: 23px; +`; + +// styled component : 회원가입 버튼 +const SignUpButton = styled(Button)``; + +// styled component : OR 텍스트 부분 +const OrDiv = styled.div` + display: flex; + align-items: center; + margin: 1.5rem; + justify-content: center; +`; + +const OrLine = styled.div` + border-bottom: 1px solid #d9d9d9; + width: 100%; +`; + +const OrText = styled.div` + margin: 0 10px; + font-weight: bold; + font-size: small; + color: #959595; +`; + +// styled component : 카카오 로그인 버튼 +const KakaoButton = styled.button` + background-color: #fee500; + color: #000; + border-radius: 12px; + padding: 10px 24px; + display: flex; + justify-content: center; + align-items: center; + font-size: 16px; + border: none; + cursor: pointer; + width: 100%; + margin-bottom: 2rem; + + &:hover { + background-color: #fee50088; + } + + // 심볼 아이콘 + &::before { + content: ""; + display: inline-block; + width: 24px; + height: 24px; + background-image: url("src/assets/images/logo_kakao.png"); + background-size: cover; + margin-right: 12px; + } +`; + +const LoginBox = ({ userData }: PropsData) => { + const navigate = useNavigate(); + const dispatch = useDispatch(); + + // 입력 값 상태관리 + const [formData, setFormData] = useState({ ...userData }); + + // 에러 메시지 + const [emailMessage, setEmailMessage] = useState(""); + const [passwordMessage, setPasswordMessage] = useState(""); + + // 입력값 변경시 이벤트 핸들러 + const changeHandler = (e: React.ChangeEvent) => { + // 입력값 유효성 검증 + const { name, value } = e.target; + setFormData({ ...formData, [e.target.name]: e.target.value }); + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*])[^\s]{8,16}$/; + + if (name === "email") { + if (!emailRegex.test(value)) { + setEmailMessage(" * 이메일 형식이 잘못되었거나 공백을 포함할 수 없습니다."); + } else { + setEmailMessage(""); + } + } + + if (name === "password") { + if (!passwordRegex.test(value)) { + setPasswordMessage( + " * 비밀번호는 영문, 특수문자, 숫자를 포함한 8~16자여야 하며, 공백을 포함할 수 없습니다." + ); + } else { + setPasswordMessage(""); + } + } + }; + + // 로그인 버튼 클릭시 이벤트 핸들러 + const loginHandler = async (e: React.FormEvent) => { + e.preventDefault(); + if (!emailMessage && !passwordMessage && formData.email && formData.password) { + try { + const response = await fetch("https://kdt.frontend.5th.programmers.co.kr:5004/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + if (response.ok) { + const data = await response.json(); + console.log("로그인 성공", data); + dispatch({ type: LOGIN }); + navigate("/"); + } else { + const errorData = await response.json(); + console.log("로그인 실패", errorData); + alert("로그인에 실패했습니다. 이메일과 비밀번호를 정확히 입력해 주세요."); + } + } catch (error) { + console.error("API 호출 중 오류 발생", error); + alert("로그인 중 오류가 발생했습니다. 다시 시도해주세요."); + } + } + }; + + return ( + + navigate("/")} /> +
+ + {emailMessage} + + {passwordMessage} + + 로그인 + + + + OR + + + 카카오로 로그인 + +
+ 계정이 없으신가요? + navigate("/signup")}> + 회원 가입 + +
{" "} +
+ ); +}; + +export default LoginBox; diff --git a/src/components/SignupBox.tsx b/src/components/SignupBox.tsx new file mode 100644 index 0000000..1a2f55b --- /dev/null +++ b/src/components/SignupBox.tsx @@ -0,0 +1,182 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { Input, Button } from "antd"; +import { useDispatch, useSelector } from "react-redux"; +import { LOGIN } from "../features/redux/store"; + +//UserData 정의 +interface UserData { + email: string; + fullName: string; + password: string; +} + +//props 정의 +interface PropsData { + userData: UserData; +} + +// styled component : Box +const BoxStyle = styled.div` + width: 500px; + height: 600px; + background-color: #ffffff; + padding: 30px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + border: 1px solid #efefef; // 확인용 + + form { + width: 100%; + } +`; + +// styled component : Logo +const LogoStyle = styled.img` + width: 250px; + height: 50px; + margin-bottom: 50px; +`; + +// styled component : Input +const InputStyle = styled(Input)` + padding: 15px; +`; + +// styled component : SubmitButton +const SubmitButton = styled(Button)` + padding: 23px; + margin-bottom: 15px; +`; + +// styled component : LoginButton +const LoginButton = styled(Button)``; + +// styled component : ErrorMessage +const ErrorMessage = styled.div` + color: red; + margin: 5px 0; + height: 37px; + overflow: hidden; + min-height: 20px; + font-size: 15px; +`; + +const SignupBox = ({ userData }: PropsData) => { + const navigate = useNavigate(); + const dispatch = useDispatch(); + + // 입력 값 상태관리 + const [formData, setFormData] = useState({ ...userData }); + + // 에러 메시지 + const [emailMessage, setEmailMessage] = useState(""); + const [fullNameMessage, setFullNameMessage] = useState(""); + const [passwordMessage, setPasswordMessage] = useState(""); + + // 입력값 변경 시 이벤트 핸들러 + const changeHandler = (e: React.ChangeEvent) => { + // 입력값 유효성 검증 + const { name, value } = e.target; + setFormData({ ...formData, [e.target.name]: e.target.value }); + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + const fullNameRegex = /^[^\s]{1,8}$/; + const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*])[^\s]{8,16}$/; + + if (name === "email") { + if (!emailRegex.test(value)) { + setEmailMessage(" * 이메일 형식이 잘못되었거나 공백을 포함할 수 없습니다."); + } else { + setEmailMessage(""); + } + } + + if (name === "fullName") { + if (!fullNameRegex.test(value)) { + setFullNameMessage(" * 이름은 8자 이하여야 하고, 공백을 포함할 수 없습니다."); + } else { + setFullNameMessage(""); + } + } + + if (name === "password") { + if (!passwordRegex.test(value)) { + setPasswordMessage( + " * 비밀번호는 영문, 특수문자, 숫자를 포함한 8~16자여야 하며, 공백을 포함할 수 없습니다." + ); + } else { + setPasswordMessage(""); + } + } + }; + + // 회원가입 버튼 클릭 시 이벤트 핸들러 + const signUpHandler = async (e: React.FormEvent) => { + e.preventDefault(); + if ( + !emailMessage && + !fullNameMessage && + !passwordMessage && + formData.email && + formData.fullName && + formData.password + ) { + try { + const response = await fetch("https://kdt.frontend.5th.programmers.co.kr:5004/signup", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + if (response.ok) { + const data = await response.json(); + console.log("로그인 성공", data); + dispatch({ type: LOGIN }); + navigate("/"); + } else { + const errorData = await response.json(); + console.log("회원가입 실패", errorData); + alert("회원가입에 실패했습니다. 이메일과 이름, 비밀번호를 정확히 입력해 주세요."); + } + } catch (error) { + console.error("API 호출 중 오류 발생", error); + alert("회원가입 중 오류가 발생했습니다. 다시 시도해주세요."); + } + } + }; + + return ( + + navigate("/")} /> +
+ + {emailMessage} + + {fullNameMessage} + + {passwordMessage} + + 회원 가입 + + + + 계정이 있으신가요? + navigate("/signin")}> + 로그인 + + +
+ ); +}; + +export default SignupBox; diff --git a/src/features/redux/store.ts b/src/features/redux/store.ts index 8a2dd06..c3a575e 100644 --- a/src/features/redux/store.ts +++ b/src/features/redux/store.ts @@ -1,9 +1,38 @@ -import { configureStore } from "@reduxjs/toolkit"; +import { configureStore, combineReducers } from "@reduxjs/toolkit"; import counterReducer from "../counter/counterSlice"; -export default configureStore({ - reducer: { - counter: counterReducer, - }, +// 초기 상태 정의 +const initialState = { + isLogin: false, +}; + +// 액션 타입 정의 +export const LOGIN = "LOGIN"; +export const LOGOUT = "LOGOUT"; + +// 사용자 리듀서 함수 정의 +const userReducer = (state = initialState, action: any) => { + switch (action.type) { + case LOGIN: + return { ...state, isLogin: true }; + case LOGOUT: + return { ...state, isLogin: false }; + default: + return state; + } +}; + +// 리듀서 결합 +const rootReducer = combineReducers({ + counter: counterReducer, + // 사용자 리듀서 추가 + user: userReducer, }); + +// 스토어 생성 +const store = configureStore({ + reducer: rootReducer, +}); + +export default store; diff --git a/src/features/redux/type.ts b/src/features/redux/type.ts index 82e5782..f4feb08 100644 --- a/src/features/redux/type.ts +++ b/src/features/redux/type.ts @@ -6,4 +6,8 @@ export interface ReduxProviderProps { export type RootState = ReturnType; +export interface UserLoginState { + isLogin: boolean; +} + export type AppDispatch = typeof store.dispatch; diff --git a/src/main.tsx b/src/main.tsx index 4293ccf..3a57f3c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -11,6 +11,7 @@ import PostCreatePage from "./pages/PostCreatePage"; import PostModifyPage from "./pages/PostModifyPage"; import ProfilePage from "./pages/ProfilePage"; import ReduxProvider from "./features/redux/Provider"; +import NotFoundPage from "./pages/NotFoundPage"; const router = createBrowserRouter([ // 로그인 페이지 @@ -48,6 +49,11 @@ const router = createBrowserRouter([ path: "/profile/:userId", element: , }, + // 404 페이지 + { + path: "/*", + element: , + }, ]); createRoot(document.getElementById("root")!).render( diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx new file mode 100644 index 0000000..496b295 --- /dev/null +++ b/src/pages/NotFoundPage.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { Button, Result } from "antd"; + +const PageStyle = styled.div` + width: 1055px; + height: 645px; + display: flex; + justify-content: center; + align-items: center; + margin: 30px auto 0; + border: 1px solid #efefef; +`; + +const NotFoundPage = () => { + const navigate = useNavigate(); + + return ( + + navigate("/")}> + 메인으로 + + } + /> + + ); +}; + +export default NotFoundPage; diff --git a/src/pages/SignInPage.tsx b/src/pages/SignInPage.tsx index edd486f..8e86df7 100644 --- a/src/pages/SignInPage.tsx +++ b/src/pages/SignInPage.tsx @@ -1,5 +1,34 @@ +import styled from "styled-components"; +import LoginBox from "../components/LoginBox"; + +interface UserData { + email: string; + password: string; +} + +// 로그인 페이지 스타일 지정 +const PageStyle = styled.div` + width: 1920px; + height: 1080px; + background-color: #f9f9f9; + display: flex; + justify-content: center; + align-items: center; + margin: 0 auto; +`; + +const userData: UserData = { + email: "", + password: "", +}; + const SignInPage = () => { - return
SignInPage
; + return ( + + {/* SignInPage */} + + + ); }; export default SignInPage; diff --git a/src/pages/SignUpPage.tsx b/src/pages/SignUpPage.tsx index b49703c..8e29ebb 100644 --- a/src/pages/SignUpPage.tsx +++ b/src/pages/SignUpPage.tsx @@ -1,5 +1,35 @@ +import styled from "styled-components"; +import SignupBox from "../components/SignupBox"; + +interface UserData { + email: string; + fullName: string; + password: string; +} +// 회원가입 페이지 스타일 지정 +const PageStyle = styled.div` + width: 1920px; + height: 1080px; + background-color: #f9f9f9; + display: flex; + justify-content: center; + align-items: center; + margin: 0 auto; +`; + +const userData: UserData = { + email: "", + fullName: "", + password: "", +}; + const SignUpPage = () => { - return
SignUpPage
; + return ( + + {/* SignUpPage */} + + + ); }; export default SignUpPage;