diff --git a/package-lock.json b/package-lock.json index 818d210..bced629 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,17 @@ "name": "ctd-esp-fe1-final", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^1.9.2", + "@types/node": "^18.13.0", + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@types/react-redux": "^7.1.25", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", "react-scripts": "^5.0.1", + "typescript": "^4.9.5", "web-vitals": "^3.1.1" } }, @@ -2843,6 +2850,29 @@ "node": ">= 8" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.2.tgz", + "integrity": "sha512-5ZAZ7hwAKWSii5T6NTPmgIBUqyVdlDs+6JjThz6J6dmHLDm6zCzv2OjHIFAi3Vvs1qjmXU0bm6eBojukYXjVMQ==", + "dependencies": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", @@ -3303,6 +3333,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -3353,9 +3392,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "16.11.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz", - "integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==" + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -3367,6 +3406,11 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", "integrity": "sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==" }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, "node_modules/@types/q": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", @@ -3382,6 +3426,35 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "node_modules/@types/react": { + "version": "18.0.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.27.tgz", + "integrity": "sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.10", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", + "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-redux": { + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -3395,6 +3468,11 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -3435,6 +3513,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -5705,6 +5788,11 @@ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7957,6 +8045,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -13286,6 +13387,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -13431,6 +13575,22 @@ "node": ">=6.0.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -13573,6 +13733,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -15044,7 +15209,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15177,6 +15341,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -18158,6 +18330,17 @@ } } }, + "@reduxjs/toolkit": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.2.tgz", + "integrity": "sha512-5ZAZ7hwAKWSii5T6NTPmgIBUqyVdlDs+6JjThz6J6dmHLDm6zCzv2OjHIFAi3Vvs1qjmXU0bm6eBojukYXjVMQ==", + "requires": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + } + }, "@remix-run/router": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", @@ -18485,6 +18668,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -18535,9 +18727,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/node": { - "version": "16.11.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz", - "integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==" + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==" }, "@types/parse-json": { "version": "4.0.0", @@ -18549,6 +18741,11 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", "integrity": "sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==" }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, "@types/q": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", @@ -18564,6 +18761,35 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "@types/react": { + "version": "18.0.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.27.tgz", + "integrity": "sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.0.10", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", + "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-redux": { + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -18577,6 +18803,11 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" }, + "@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, "@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -18617,6 +18848,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -20280,6 +20516,11 @@ } } }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -21913,6 +22154,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25624,6 +25880,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -25727,6 +26003,20 @@ "minimatch": "^3.0.5" } }, + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -25838,6 +26128,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -26917,8 +27212,7 @@ "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" }, "unbox-primitive": { "version": "1.0.2", @@ -27000,6 +27294,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 1db476a..cfa2979 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,17 @@ "version": "0.1.0", "private": true, "dependencies": { + "@reduxjs/toolkit": "^1.9.2", + "@types/node": "^18.13.0", + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@types/react-redux": "^7.1.25", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", "react-scripts": "^5.0.1", + "typescript": "^4.9.5", "web-vitals": "^3.1.1" }, "scripts": { diff --git a/src/App.jsx b/src/App.tsx similarity index 100% rename from src/App.jsx rename to src/App.tsx diff --git a/src/componentes/botones/boton-favorito.componente.jsx b/src/componentes/botones/boton-favorito.componente.jsx deleted file mode 100644 index 81c56e5..0000000 --- a/src/componentes/botones/boton-favorito.componente.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import './boton-favorito.css'; -/** - * Boton que indica si un elemento es favorito o no, y da la posibilidad de marcarlo/desmarcarlo - * - * Deberás tipar las propiedades si usas este componente - * - * - * @returns un JSX element - */ -const BotonFavorito = ({esFavorito, onClick}) => { - const src = esFavorito ? "/imagenes/star-filled.png" : "/imagenes/star.png" - - return
- {"favorito"} -
-} - -export default BotonFavorito; \ No newline at end of file diff --git a/src/componentes/botones/boton-favorito.componente.tsx b/src/componentes/botones/boton-favorito.componente.tsx new file mode 100644 index 0000000..1ffef93 --- /dev/null +++ b/src/componentes/botones/boton-favorito.componente.tsx @@ -0,0 +1,41 @@ + +import './boton-favorito.css'; +import { useAppDispatch, useAppSelector } from '../../store'; +import { ADD_FAVORITOS } from '../../store/characters/slice'; +import { ICharacter } from '../../interfaces/character.interface'; +/** + * Boton que indica si un elemento es favorito o no, y da la posibilidad de marcarlo/desmarcarlo + * + * Deberás tipar las propiedades si usas este componente + * + * + * @returns un JSX element + */ +export interface IBotonFavorito { + isFavorite: boolean; + source: string + id: number +} + +const BotonFavorito = ({isFavorite, id, name, image }: ICharacter) => { + const dispatch = useAppDispatch() + useAppSelector((state) => state.characters) + + + +/** + * Disparador de Agregar a favoritos + * @author 'Carmen Vargas' + * @return {Array} un array de favoritos + */ + const addFavorito = () => { + console.log('Botón Favorito ON CLICK'); + dispatch(ADD_FAVORITOS( {isFavorite, id, name, image} )); + } + const src = isFavorite ? "/imagenes/star-filled.png" : "/imagenes/star.png" + return
+ {"favorito"} +
+} + +export default BotonFavorito; \ No newline at end of file diff --git a/src/componentes/episodios/tarjeta-episodio.componente.jsx b/src/componentes/episodios/tarjeta-episodio.componente.jsx deleted file mode 100644 index 11cd257..0000000 --- a/src/componentes/episodios/tarjeta-episodio.componente.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import './tarjeta-episodio.css'; - -/** - * Tarjeta para cada episodio dentro de la vista de personaje. - * - * Deberás agregar las propiedades necesarias para mostrar los datos de los episodios - * - * - * @returns un JSX element - */ -const TarjetaEpisodio = () => { - - return
-

Close Rick-counters of the Rick Kind

-
- S01E01 - Lanzado el: April 7, 2014 -
-
-} - -export default TarjetaEpisodio; \ No newline at end of file diff --git a/src/componentes/episodios/tarjeta-episodio.componente.tsx b/src/componentes/episodios/tarjeta-episodio.componente.tsx new file mode 100644 index 0000000..7b85fba --- /dev/null +++ b/src/componentes/episodios/tarjeta-episodio.componente.tsx @@ -0,0 +1,27 @@ +import './tarjeta-episodio.css'; + +/** + * Tarjeta para cada episodio dentro de la vista de personaje. + * + * Deberás agregar las propiedades necesarias para mostrar los datos de los episodios + * + * + * @returns un JSX element + */ +export interface IEpisodio { + nombre: string; + numeroDeEpisodio: string; + fechaDeLanzamiento: Date; +} +const TarjetaEpisodio = ({nombre,numeroDeEpisodio,fechaDeLanzamiento}: IEpisodio) => { + const dateString = fechaDeLanzamiento.toLocaleDateString() + return
+

{nombre}

+
+ {numeroDeEpisodio} + Lanzado el:{dateString} +
+
+} + +export default TarjetaEpisodio; \ No newline at end of file diff --git a/src/componentes/paginacion/paginacion.componente.jsx b/src/componentes/paginacion/paginacion.componente.jsx deleted file mode 100644 index 53804a3..0000000 --- a/src/componentes/paginacion/paginacion.componente.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import './paginacion.css'; - -/** - * Componente que contiene los botones para paginar - * - * Deberás agregar las propiedades necesarias para que funcione correctamente - * - * - * @returns un JSX element - */ -const Paginacion = () => { - - return
- - -
-} - -export default Paginacion; \ No newline at end of file diff --git a/src/componentes/paginacion/paginacion.componente.tsx b/src/componentes/paginacion/paginacion.componente.tsx new file mode 100644 index 0000000..8320000 --- /dev/null +++ b/src/componentes/paginacion/paginacion.componente.tsx @@ -0,0 +1,35 @@ +import './paginacion.css'; +import { useAppDispatch, useAppSelector } from '../../store'; + +import { GET_CHARACTERS } from '../../store/characters/thunk'; + +/** + * Componente que contiene los botones para paginar + * + * Deberás agregar las propiedades necesarias para que funcione correctamente + * + * + * @returns un JSX element + */ +export interface IPaginacion { + pageValue: number + } + +const Paginacion = () => { + const dispatch = useAppDispatch() + const {nextPage, prevPage} = useAppSelector((state)=> state.characters) + + const handelNextPage = () =>{ + dispatch(GET_CHARACTERS(nextPage)) + } + const handelPrevPage = () =>{ + dispatch(GET_CHARACTERS(prevPage)) + } + return
+ + + +
+} + +export default Paginacion; \ No newline at end of file diff --git a/src/componentes/personajes/filtros.componente.jsx b/src/componentes/personajes/filtros.componente.jsx deleted file mode 100644 index b82f7c3..0000000 --- a/src/componentes/personajes/filtros.componente.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import './filtros.css'; - -const Filtros = () => { - - return
- - -
-} - -export default Filtros; \ No newline at end of file diff --git a/src/componentes/personajes/filtros.componente.tsx b/src/componentes/personajes/filtros.componente.tsx new file mode 100644 index 0000000..7bcb9b5 --- /dev/null +++ b/src/componentes/personajes/filtros.componente.tsx @@ -0,0 +1,45 @@ +import { useRef, useEffect } from 'react'; +import './filtros.css'; +import { useAppDispatch } from '../../store'; +import { IFiltros } from './personaje.interface'; +import { GET_CHARACTERS, GET_CHARACTERS_FILTER } from '../../store/characters/thunk'; + + + +const Filtros = ({name, setName, urlBase}: IFiltros) => { + const ref = useRef(null) + const dispatch = useAppDispatch() + + const deleteFilter = ()=>{ + if (!ref.current) return + ref.current.value = '' + dispatch(GET_CHARACTERS(urlBase)) + + } + + + const filterByName = () => { + if (!ref.current) return + setName(ref.current.value) + if (name?.trim === null) { + ref.current.value = '' + return + } + console.log(name) + dispatch(GET_CHARACTERS_FILTER({name})) + + } + + useEffect(() => { + if(name === null){ + deleteFilter() + } + }, [name]); + + return
+ + +
+ } + + export default Filtros; \ No newline at end of file diff --git a/src/componentes/personajes/grilla-personajes.componente.jsx b/src/componentes/personajes/grilla-personajes.componente.jsx deleted file mode 100644 index 759b71b..0000000 --- a/src/componentes/personajes/grilla-personajes.componente.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import './grilla-personajes.css'; -import TarjetaPersonaje from './tarjeta-personaje.componente'; - -/** - * Grilla de personajes para la pagina de inicio - * - * Deberás agregar las funciones necesarias para mostrar y paginar los personajes - * - * - * @returns un JSX element - */ -const GrillaPersonajes = () => { - - return
- - - -
-} - -export default GrillaPersonajes; \ No newline at end of file diff --git a/src/componentes/personajes/grilla-personajes.componente.tsx b/src/componentes/personajes/grilla-personajes.componente.tsx new file mode 100644 index 0000000..2fe3f59 --- /dev/null +++ b/src/componentes/personajes/grilla-personajes.componente.tsx @@ -0,0 +1,42 @@ +import { useAppSelector } from '../../store'; +import './grilla-personajes.css'; +import TarjetaPersonaje from './tarjeta-personaje.componente'; +import { IGrillaPersonajes } from './personaje.interface'; + + + + +/** + * Grilla de personajes para la pagina de inicio + * + * Deberás agregar las funciones necesarias para mostrar y paginar los personajes + * + * + * @returns un JSX element + */ +const GrillaPersonajes = ({initialCharacters} : IGrillaPersonajes) => { + + const {isError, isLoading, listFavoritos} = useAppSelector((state) => state.characters) + // Crea una copia de la lista de favoritos con los IDs de los personajes + const favoritosIds = listFavoritos.map(character => character.id); + + // Mapea los personajes iniciales y establece esFavorito en true si están en la lista de favoritos + const charactersWithFavoritos = initialCharacters.map(character => ({ + ...character, + isFavorite: favoritosIds.includes(character.id), + })); + + return
+ + {isLoading ?

Loading...

: + + charactersWithFavoritos?.map(character => + + + )} + { isError &&

{isError}

} + +
+ } + + export default GrillaPersonajes; \ No newline at end of file diff --git a/src/componentes/personajes/personaje.interface.ts b/src/componentes/personajes/personaje.interface.ts new file mode 100644 index 0000000..6e3e3d2 --- /dev/null +++ b/src/componentes/personajes/personaje.interface.ts @@ -0,0 +1,13 @@ +import { ICharacter } from "../../interfaces/character.interface"; + +export interface IFiltros{ + name: string | null, + setName: (name: string | null )=> void + urlBase: string + } + + + + export interface IGrillaPersonajes{ + initialCharacters: ICharacter[] + } diff --git a/src/componentes/personajes/tarjeta-personaje.componente.jsx b/src/componentes/personajes/tarjeta-personaje.componente.tsx similarity index 56% rename from src/componentes/personajes/tarjeta-personaje.componente.jsx rename to src/componentes/personajes/tarjeta-personaje.componente.tsx index 332edfe..14bb1ad 100644 --- a/src/componentes/personajes/tarjeta-personaje.componente.jsx +++ b/src/componentes/personajes/tarjeta-personaje.componente.tsx @@ -1,5 +1,7 @@ import BotonFavorito from '../botones/boton-favorito.componente'; import './tarjeta-personaje.css'; +import { ICharacter } from '../../interfaces/character.interface'; + /** * Tarjeta para cada personaje dentro de la grilla de personajes. @@ -7,15 +9,15 @@ import './tarjeta-personaje.css'; * Deberás agregar las propiedades necesarias para mostrar los datos de los personajes * * - * @returns un JSX element +* @returns un JSX element */ -const TarjetaPersonaje = () => { +const TarjetaPersonaje = ({name, image, isFavorite, id}: ICharacter) => { return
- Rick Sanchez + {name}/
- Rick Sanchez - + {name} +
} diff --git a/src/index.jsx b/src/index.tsx similarity index 63% rename from src/index.jsx rename to src/index.tsx index 44f96ac..b22cfe0 100644 --- a/src/index.jsx +++ b/src/index.tsx @@ -1,20 +1,24 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import './index.css'; -import App from './App'; +import { Provider } from "react-redux"; import reportWebVitals from './reportWebVitals'; import {BrowserRouter} from "react-router-dom"; +import store from "./store"; +import App from "./App"; + + ReactDOM.render( - - - - - , - document.getElementById('root') + + + + + , +document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +reportWebVitals(); \ No newline at end of file diff --git a/src/interfaces/character.interface.ts b/src/interfaces/character.interface.ts new file mode 100644 index 0000000..69afdf4 --- /dev/null +++ b/src/interfaces/character.interface.ts @@ -0,0 +1,12 @@ +export interface ICharacter { + id:number; + name: string; + image: string; + isFavorite: boolean; + +} +export interface IAllCharacters{ + allCharacters:ICharacter[] + nextPage: string; + prevPage: string; +} \ No newline at end of file diff --git a/src/interfaces/episodio.interface.ts b/src/interfaces/episodio.interface.ts new file mode 100644 index 0000000..3d54e08 --- /dev/null +++ b/src/interfaces/episodio.interface.ts @@ -0,0 +1,9 @@ +export interface episodio { + id: number; + name: string; + air_date: string; + episode: string; + characters: string[]; + url: string; + created: Date; +} \ No newline at end of file diff --git a/src/paginas/Detalle.pagina.jsx b/src/paginas/Detalle.pagina.tsx similarity index 62% rename from src/paginas/Detalle.pagina.jsx rename to src/paginas/Detalle.pagina.tsx index b0db4c7..f87c0e2 100644 --- a/src/paginas/Detalle.pagina.jsx +++ b/src/paginas/Detalle.pagina.tsx @@ -1,6 +1,7 @@ import "./Detalle.css"; import BotonFavorito from "../componentes/botones/boton-favorito.componente"; import TarjetaEpisodio from "../componentes/episodios/tarjeta-episodio.componente"; +import { IEpisodio } from "../componentes/episodios/tarjeta-episodio.componente"; /** * Esta es la pagina de detalle. Aqui se puede mostrar la vista sobre el personaje seleccionado junto con la lista de episodios en los que aparece @@ -15,6 +16,12 @@ import TarjetaEpisodio from "../componentes/episodios/tarjeta-episodio.component * @returns la pagina de detalle */ const PaginaDetalle = () => { + const episodio: IEpisodio = { + nombre: 'Juan', + numeroDeEpisodio: '1l', + fechaDeLanzamiento: new Date() + } + return

Rick Sanchez

@@ -26,14 +33,14 @@ const PaginaDetalle = () => {

Planeta: Earth

Genero: Male

- + {/* */}

Lista de episodios donde apareció el personaje

- - - + + +
} diff --git a/src/paginas/Favoritos.pagina.jsx b/src/paginas/Favoritos.pagina.jsx deleted file mode 100644 index 5187d1e..0000000 --- a/src/paginas/Favoritos.pagina.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import GrillaPersonajes from "../componentes/personajes/grilla-personajes.componente"; - -/** - * Esta es la pagina de favoritos. Aquí se deberan ver todos los personajes marcados como favoritos - * - * Uso: - * ``` ``` - * - * @returns la pagina de favoritos - */ -const PaginaFavoritos = () => { - return
-
-

Personajes Favoritos

- -
- -
-} - -export default PaginaFavoritos \ No newline at end of file diff --git a/src/paginas/Favoritos.pagina.tsx b/src/paginas/Favoritos.pagina.tsx new file mode 100644 index 0000000..ca229a8 --- /dev/null +++ b/src/paginas/Favoritos.pagina.tsx @@ -0,0 +1,55 @@ +import { useEffect } from "react"; +import GrillaPersonajes from "../componentes/personajes/grilla-personajes.componente"; +import { useAppDispatch, useAppSelector } from "../store"; +import { GET_CHARACTERS } from "../store/characters/thunk"; + +/** + * Esta es la pagina de favoritos. Aquí se deberan ver todos los personajes marcados como favoritos + * + * Uso: + * ``` ``` + * + * @returns la pagina de favoritos + */ +const PaginaFavoritos = () => { + const dispatch = useAppDispatch() + const { listFavoritos, urlBase} = useAppSelector((state) => state.characters) + useEffect(() => { + dispatch(GET_CHARACTERS(urlBase)) + + .then((response) => { + // Handle a successful API request here + console.log("Characters fetched successfully:", response); + }) + .catch((error) => { + // Handle API request errors here + console.error("Error fetching characters:", error); + }); + }, [dispatch,urlBase]); + useEffect(() => { + const fetchData = async () => { + try { + const response = await dispatch(GET_CHARACTERS(urlBase)); + // Handle a successful API request here + console.log("Characters fetched successfully:", response); + } catch (error) { + // Handle API request errors here + console.error("Error fetching characters:", error); + } + }; + + fetchData(); + }, [dispatch,urlBase]); + + + + return
+
+

Personajes Favoritos

+ +
+ +
+} + +export default PaginaFavoritos \ No newline at end of file diff --git a/src/paginas/Inicio.pagina.jsx b/src/paginas/Inicio.pagina.jsx deleted file mode 100644 index bb2b011..0000000 --- a/src/paginas/Inicio.pagina.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import Filtros from "../componentes/personajes/filtros.componente" -import GrillaPersonajes from "../componentes/personajes/grilla-personajes.componente" -import Paginacion from "../componentes/paginacion/paginacion.componente"; - -/** - * Esta es la pagina principal. Aquí se debera ver el panel de filtros junto con la grilla de personajes. - * - * Uso: - * ``` ``` - * - * @returns la pagina de inicio - */ -const PaginaInicio = () => { - return
-
-

Catálogo de Personajes

- -
- - - - -
-} - -export default PaginaInicio \ No newline at end of file diff --git a/src/paginas/Inicio.pagina.tsx b/src/paginas/Inicio.pagina.tsx new file mode 100644 index 0000000..475a229 --- /dev/null +++ b/src/paginas/Inicio.pagina.tsx @@ -0,0 +1,48 @@ +import Filtros from "../componentes/personajes/filtros.componente" +import GrillaPersonajes from "../componentes/personajes/grilla-personajes.componente" +import Paginacion from "../componentes/paginacion/paginacion.componente"; +import { useAppDispatch, useAppSelector } from "../store"; +import { useEffect, useRef, useState } from "react"; +import { GET_CHARACTERS } from "../store/characters/thunk"; + +/** + * Esta es la pagina principal. Aquí se debera ver el panel de filtros junto con la grilla de personajes. + * + * Uso: + * ``` ``` + * + * @returns la pagina de inicio + */ + +const PaginaInicio = () => { + const dispatch = useAppDispatch() + const { allCharacters, urlBase} = useAppSelector((state) => state.characters) + const ref = useRef(null) + + + const [name, setName] = useState(''); + + const handleCleanFilter = () =>{ + setName(null) + } + + useEffect(() => { + dispatch(GET_CHARACTERS(urlBase)); + }, []) + + return
+
+

Catálogo de Personajes

+ +k
+ + + + + + +
+} + + +export default PaginaInicio \ No newline at end of file diff --git a/src/store/characters/slice.ts b/src/store/characters/slice.ts new file mode 100644 index 0000000..a662436 --- /dev/null +++ b/src/store/characters/slice.ts @@ -0,0 +1,130 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import { GET_CHARACTERS, GET_CHARACTERS_FILTER, } from './thunk'; +import { IAllCharacters, ICharacter} from '../../interfaces/character.interface'; + + + +export type CharacterState = { +urlBase: string +allCharacters: ICharacter[], +isLoading: boolean, +isError: string | null, +nextPage: string, +prevPage: string, +esfavorito: boolean +listFavoritos: ICharacter[] + +} + + +const initialState : CharacterState= { + urlBase: 'https://rickandmortyapi.com/api/character/', + allCharacters: [], + isLoading: true, + isError : null, + prevPage: '', + nextPage: '', + esfavorito: false, + listFavoritos: [] +} + + +export const charactersSlice = createSlice({ + name : 'character', + initialState: initialState, + reducers : { + /** + * Agrega a favoritos un nuevo personaje agregándolo al estado de la propiedad listfavoritos[] + * @author 'Carmen Vargas' + * @param { WritableDraft } state + * @return {Object} retorna el objeto, personaje agregado al array de favoritos + * @param {PayloadAction} action + * + */ + ADD_FAVORITOS: (state, action: PayloadAction) => { + const personaje = action.payload; + const esFavorito = state.listFavoritos.find( + (fav) => fav.id === personaje.id + ); + if (esFavorito) { + esFavorito.isFavorite = false; + state.listFavoritos = state.listFavoritos.filter( + (item) => item.id !== esFavorito.id + ); + } else { + personaje.isFavorite = true; + state.listFavoritos.push(personaje); + } + }, + + + /** + * Limpia el estado del array de favoritos para dejarlo como array vacío + * @author 'Carmen Vargas' + * @param {WritableDraft} state + * @return {Array} retorna un array vacío para el estado de listFavoritos[] + */ + CLEAR_ALL_FAVORITES: (state) => { + state.listFavoritos = [] + } + + }, + extraReducers : (builder) =>{ + + /** + * Usa el método GET_CHARACTES para guardar en el estado del array allCharacters además, g + * uarda los estados de la página anterior y la siguiente + * @author 'Carmen Vargas' + * @param { WritableDraft} state + * @param {PayloadAction} action + * @return {Array} retorna un array de personajes + */ + + builder.addCase(GET_CHARACTERS.pending, ( state ) => { + state.isLoading = true; + }) + + builder.addCase(GET_CHARACTERS.fulfilled, (state, action : PayloadAction) => { + state.allCharacters = action.payload.allCharacters; + state.prevPage = action.payload.prevPage + state.nextPage = action.payload.nextPage + state.isLoading = false; + }) + + builder.addCase(GET_CHARACTERS.rejected, ( state, action ) => { + state.isLoading = false; + state.isError = action.error.message ?? 'Ha ocurrido un error' + + + }) + + /** + * Usa el método GET_CHARACTES_FILTER para filtrar por nombre. Necesita recibir el nombre buscado que se ingresa en el input. + * @author 'Carmen Vargas' + * @return {Array} retorna un array de personajes filtrados por nombre de tipo IAllCharacters + */ + builder.addCase(GET_CHARACTERS_FILTER.pending, ( state ) => { + state.isLoading = true; + }) + + builder.addCase(GET_CHARACTERS_FILTER.fulfilled, (state, action : PayloadAction) => { + state.allCharacters = action.payload.allCharacters; + state.prevPage = action.payload.prevPage + state.nextPage = action.payload.nextPage + state.isLoading = false; + }) + + builder.addCase(GET_CHARACTERS_FILTER.rejected, ( state, action ) => { + state.isLoading = false; + state.isError = action.error.message ?? 'Ha ocurrido un error' + + + }) + }, + + }); + + +const characterReducer = charactersSlice.reducer; +export const {ADD_FAVORITOS} = charactersSlice.actions; +export default characterReducer; diff --git a/src/store/characters/thunk.ts b/src/store/characters/thunk.ts new file mode 100644 index 0000000..19abd80 --- /dev/null +++ b/src/store/characters/thunk.ts @@ -0,0 +1,51 @@ +import {createAsyncThunk} from '@reduxjs/toolkit'; +import { IAllCharacters, ICharacter } from '../../interfaces/character.interface' + +export const GET_CHARACTERS = createAsyncThunk( + 'character/GET_CHARACTERS', + async (urlBase: string): Promise => { + + try { + const resp = await fetch(urlBase); + const data = await resp.json(); + const resultsCharacters = { + allCharacters: data.results, + nextPage: data.info.next, + prevPage: data.info.prev + } + return resultsCharacters; + } catch (error) { + console.error('Error fetching data:', error); + throw error; + } + + } + ); + + export const GET_CHARACTERS_FILTER = createAsyncThunk( + 'character/GET_CHARACTERS_FILTER', + async ({name}: {name: string | null | undefined}): Promise => { + + try { + const resp = await fetch(`https://rickandmortyapi.com/api/character/?name=${name}`) + const data = await resp.json(); + const resultsCharacters: IAllCharacters = { + allCharacters: data.results, + nextPage: data.info.next, + prevPage: data.info.prev + } + return resultsCharacters; + } catch (error) { + console.error('Error fetching data:', error); + throw error; + } + + } + ); + + +export const GET_CHARACTER_ID = createAsyncThunk('character/GET_CHARACTERS_ID', async (id: number) : Promise=> { + const resp = await fetch(`https://rickandmortyapi.com/api/character/${id}`) + const data = await resp.json(); + return data; +}) \ No newline at end of file diff --git a/src/store/index.tsx b/src/store/index.tsx new file mode 100644 index 0000000..8e0eecd --- /dev/null +++ b/src/store/index.tsx @@ -0,0 +1,19 @@ +import { configureStore} from "@reduxjs/toolkit"; +import { TypedUseSelectorHook,useDispatch, useSelector } from "react-redux"; +import characterReducer from "./characters/slice"; + +const store = configureStore({ + reducer :{ + characters: characterReducer, + }, +}) + +// Tipamos el hook useSelector y useDispatch +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; + +type DispatchFunc = () => AppDispatch; + +export const useAppDispatch : DispatchFunc = useDispatch +export const useAppSelector : TypedUseSelectorHook = useSelector; +export default store; \ No newline at end of file