From f1f25803bcdb2e1040ce67845ce2803ea52bc5cc Mon Sep 17 00:00:00 2001 From: bonz88 Date: Mon, 26 Feb 2024 20:20:16 +0100 Subject: [PATCH 1/4] installed recharts js --- package-lock.json | 297 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 293 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba8337f..b3d0367 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "react-dom": "^18", "react-icons": "^5.0.1", "react-redux": "^9.1.0", + "recharts": "^2.12.1", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7" }, @@ -987,6 +988,60 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2005,8 +2060,117 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -2031,6 +2195,11 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2116,6 +2285,15 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2716,12 +2894,25 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -3249,6 +3440,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -3765,6 +3964,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4422,7 +4626,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -4491,8 +4694,7 @@ "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==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-redux": { "version": "9.1.0", @@ -4565,6 +4767,20 @@ } } }, + "node_modules/react-smooth": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.0.tgz", + "integrity": "sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -4587,6 +4803,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4606,6 +4837,36 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.1.tgz", + "integrity": "sha512-35vUCEBPf+pM+iVgSgVTn86faKya5pc4JO6cYJL63qOK2zDEyzDn20Tdj+CDI/3z+VcpKyQ8ZBQ9OiQ+vuAbjg==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -5260,6 +5521,11 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5521,6 +5787,27 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/victory-vendor": { + "version": "36.9.1", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.1.tgz", + "integrity": "sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 812f8cc..9da9d96 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react-dom": "^18", "react-icons": "^5.0.1", "react-redux": "^9.1.0", + "recharts": "^2.12.1", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7" }, From 6cd39351e361ce7d0f141e8edbd3c059420a2af8 Mon Sep 17 00:00:00 2001 From: bonz88 Date: Wed, 6 Mar 2024 22:43:23 +0100 Subject: [PATCH 2/4] added handleCarousel, i will solve it in next PR --- src/app/components/CarouselCoins.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/components/CarouselCoins.tsx b/src/app/components/CarouselCoins.tsx index 3ec4b58..51c0dd5 100644 --- a/src/app/components/CarouselCoins.tsx +++ b/src/app/components/CarouselCoins.tsx @@ -7,6 +7,7 @@ import { AppDispatch, useAppSelector } from "../../redux/store"; import { getCoinData } from "../../redux/features/coinInfoSlice"; import { ChevronUpIcon } from "../icons/ChevronUpIcon"; import { ChevronDownIcon } from "../icons/ChevronDownIcon"; + import { Carousel, CarouselContent, @@ -31,6 +32,9 @@ export default function CarouselCoins() { if (isLoading) return
Loading...
; if (hasError) return
Error loading the data.
; + const handleCarousel = (coin: string) => { + //implement in next PR + }; return ( -
+
handleCarousel(coin.id)} + className="h-[88px] dark:bg-[#191925] bg-[#FFFFFF] flex items-center rounded-lg cursor-pointer" + >
Date: Wed, 6 Mar 2024 22:44:16 +0100 Subject: [PATCH 3/4] added area and bar graph for displaying bitcoin data --- src/app/components/GraphCoins.tsx | 11 +++ src/app/components/PriceChart.tsx | 110 ++++++++++++++++++++++++ src/app/components/VolumeChart.tsx | 106 +++++++++++++++++++++++ src/app/page.tsx | 4 + src/redux/features/selectedCoinSlice.ts | 78 +++++++++++++++++ src/redux/store.ts | 2 + 6 files changed, 311 insertions(+) create mode 100644 src/app/components/GraphCoins.tsx create mode 100644 src/app/components/PriceChart.tsx create mode 100644 src/app/components/VolumeChart.tsx create mode 100644 src/redux/features/selectedCoinSlice.ts diff --git a/src/app/components/GraphCoins.tsx b/src/app/components/GraphCoins.tsx new file mode 100644 index 0000000..52fd0aa --- /dev/null +++ b/src/app/components/GraphCoins.tsx @@ -0,0 +1,11 @@ +import PriceChart from "./PriceChart"; +import VolumeChart from "./VolumeChart"; + +export default function GraphCoins() { + return ( +
+ + +
+ ); +} diff --git a/src/app/components/PriceChart.tsx b/src/app/components/PriceChart.tsx new file mode 100644 index 0000000..df78ee8 --- /dev/null +++ b/src/app/components/PriceChart.tsx @@ -0,0 +1,110 @@ +"use client"; +import { useDispatch } from "react-redux"; +import { useEffect, useState } from "react"; +import { XAxis, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts"; +import { AppDispatch, useAppSelector } from "../../redux/store"; +import { getCoinDataGraph } from "../../redux/features/selectedCoinSlice"; + +export default function PriceChart() { + const dispatch: AppDispatch = useDispatch(); + const { selectedCoins, loading, hasError } = useAppSelector( + (state) => state.selectedCoin + ); + + const currencyCode = useAppSelector( + (state) => state.currency.currentCurrency.code + ); + const currencySimbol = useAppSelector( + (state) => state.currency.currentCurrency.symbol + ); + + const [currentPriceCoin, setCurrentPriceCoin] = useState(""); + const [currentDate, setCurrentDate] = useState(""); + + useEffect(() => { + dispatch( + getCoinDataGraph({ + currency: currencyCode, + days: "3", + coinId: "bitcoin", + }) + ); + }, [dispatch, currencyCode]); + + useEffect(() => { + if (selectedCoins.length > 0) { + const lastDataPoint = + selectedCoins[0].prices[selectedCoins[0].prices.length - 1]; + const lastPrice = lastDataPoint[1]; + const lastDate = new Date(lastDataPoint[0]).toLocaleDateString(); + + setCurrentPriceCoin(`${lastPrice.toFixed(2)}`); + setCurrentDate(lastDate); + } + }, [selectedCoins, currencyCode]); + + if (loading === "pending") { + return
Loading...
; + } + + if (hasError) { + return
Error fetching data
; + } + + const coin = selectedCoins.length > 0 ? selectedCoins[0] : null; + + const formattedData = coin + ? coin.prices.map(([time, price]) => ({ + date: new Date(time).toLocaleDateString(), + price, + })) + : []; + + const handleMouseMove = (e: any) => { + if (e.activePayload && e.activePayload.length > 0) { + const { payload } = e.activePayload[0]; + setCurrentPriceCoin(`${payload.price.toFixed(2)}`); + setCurrentDate(payload.date); + } + }; + + return ( +
+
+ + {coin + ? coin.id.charAt(0).toUpperCase() + coin.id.slice(1) + : "Loading..."} + + + {currentPriceCoin + ? `${currencySimbol}${currentPriceCoin}` + : "Fetching..."} + + + {currentDate} + +
+ + + + + + + + + + + + + } /> + + +
+ ); +} diff --git a/src/app/components/VolumeChart.tsx b/src/app/components/VolumeChart.tsx new file mode 100644 index 0000000..bc41b7b --- /dev/null +++ b/src/app/components/VolumeChart.tsx @@ -0,0 +1,106 @@ +"use client"; +import { useDispatch } from "react-redux"; +import { useEffect, useState } from "react"; +import { BarChart, Bar, XAxis, Tooltip, ResponsiveContainer } from "recharts"; +import { AppDispatch, useAppSelector } from "../../redux/store"; +import { getCoinDataGraph } from "../../redux/features/selectedCoinSlice"; +import formatNumber from "../utils/formatNumber"; + +export default function VolumeChart() { + const dispatch: AppDispatch = useDispatch(); + const { selectedCoins, loading, hasError } = useAppSelector( + (state) => state.selectedCoin + ); + + const currencyCode = useAppSelector( + (state) => state.currency.currentCurrency.code + ); + const currencySimbol = useAppSelector( + (state) => state.currency.currentCurrency.symbol + ); + const [currentVolume, setCurrentVolume] = useState(""); + const [currentDateVolume, setCurrentDateVolume] = useState(""); + + useEffect(() => { + dispatch( + getCoinDataGraph({ + currency: currencyCode, + days: "3", + coinId: "bitcoin", + }) + ); + }, [dispatch, currencyCode]); + + useEffect(() => { + if (selectedCoins.length > 0) { + const lastDataPoint = + selectedCoins[0].total_volumes[ + selectedCoins[0].total_volumes.length - 1 + ]; + const lastTotalVolume = lastDataPoint[1]; + const lastDate = new Date(lastDataPoint[0]).toLocaleDateString(); + + setCurrentVolume(`${lastTotalVolume.toFixed(2)}`); + setCurrentDateVolume(lastDate); + } + }, [selectedCoins, currencyCode]); + + if (loading === "pending") { + return
Loading...
; + } + + if (hasError) { + return
Error fetching data
; + } + + const coin = selectedCoins.length > 0 ? selectedCoins[0] : null; + + const formattedDataVolume = coin + ? coin.total_volumes.map(([time, volume]) => ({ + date: new Date(time).toLocaleDateString(), + volume, + })) + : []; + + const handleMouseMoveOnVolume = (e: any) => { + if (e.activePayload && e.activePayload.length > 0) { + const { payload } = e.activePayload[0]; + setCurrentVolume(`${payload.volume.toFixed(2)}`); + setCurrentDateVolume(payload.date); + } + }; + + return ( +
+
+ + Volume 24h + + + {currentVolume + ? `${currencySimbol}${formatNumber(parseFloat(currentVolume))}` + : "Fetching..."} + + + {currentDateVolume} + +
+ + + + + + + + + + } /> + + + +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 948e893..11fffb5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,6 @@ import NavHome from "./components/NavHome"; import CarouselCoins from "./components/CarouselCoins"; +import GraphCoins from "./components/GraphCoins"; export default function Home() { return ( @@ -8,6 +9,9 @@ export default function Home() {
+
+ +
); } diff --git a/src/redux/features/selectedCoinSlice.ts b/src/redux/features/selectedCoinSlice.ts new file mode 100644 index 0000000..6046455 --- /dev/null +++ b/src/redux/features/selectedCoinSlice.ts @@ -0,0 +1,78 @@ +"use client"; + +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; + +type GetCoinDataArgs = { + currency: string; + days: string; + coinId: string; +}; + +type CoinData = { + id: string; + prices: [number, number][]; + total_volumes: [number, number][]; +}; + +type selectedCoinState = { + selectedCoins: CoinData[]; + loading: string; + hasError: boolean; +}; + +const initialState: selectedCoinState = { + selectedCoins: [], + loading: "idle", + hasError: false, +}; + +export const getCoinDataGraph = createAsyncThunk( + "coinData/getCoinData", + async ({ currency, days, coinId }: GetCoinDataArgs, { rejectWithValue }) => { + try { + const response = await fetch( + `https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=${currency}&days=${days}?x_cg_demo_api_key=${process.env.NEXT_PUBLIC_API_KEY}` + ); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + const { prices, total_volumes } = data; + const item: CoinData = { + id: coinId, + prices, + total_volumes, + }; + return item; + } catch (error: any) { + return rejectWithValue(error.message); + } + } +); + +const selectedCoinsSlice = createSlice({ + name: "selectedCoin", + initialState, + reducers: {}, + + extraReducers: (builder) => { + builder + .addCase(getCoinDataGraph.pending, (state) => { + state.loading = "pending"; + state.hasError = false; + }) + .addCase(getCoinDataGraph.fulfilled, (state, action) => { + state.selectedCoins = [action.payload]; + state.loading = "fulfilled"; + }) + .addCase(getCoinDataGraph.rejected, (state, action) => { + state.loading = "rejected"; + state.hasError = true; + console.error("API call failed with error:", action.payload); + }); + }, +}); + +export default selectedCoinsSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index bbe96ac..7dd3189 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -4,12 +4,14 @@ import { configureStore } from "@reduxjs/toolkit"; import globalReducer from "./features/globalSlice"; import currencyReducer from "./features/currencySlice"; import coinReducer from "./features/coinInfoSlice"; +import selectedCoinReducer from "./features/selectedCoinSlice"; export const store = configureStore({ reducer: { globalData: globalReducer, currency: currencyReducer, coinData: coinReducer, + selectedCoin: selectedCoinReducer, }, }); From 9a093c16cd5cccdd22889a10ffb2edb74f04e244 Mon Sep 17 00:00:00 2001 From: bonz88 Date: Thu, 7 Mar 2024 21:08:39 +0100 Subject: [PATCH 4/4] refactored to use one component for Price and Volume chart, replaced div with button inside CarouselCoins component --- src/app/components/CarouselCoins.tsx | 27 +++-- src/app/components/CoinChart.tsx | 148 +++++++++++++++++++++++++++ src/app/components/GraphCoins.tsx | 23 ++++- src/app/components/PriceChart.tsx | 110 -------------------- src/app/components/VolumeChart.tsx | 106 ------------------- 5 files changed, 180 insertions(+), 234 deletions(-) create mode 100644 src/app/components/CoinChart.tsx delete mode 100644 src/app/components/PriceChart.tsx delete mode 100644 src/app/components/VolumeChart.tsx diff --git a/src/app/components/CarouselCoins.tsx b/src/app/components/CarouselCoins.tsx index 51c0dd5..8ac1ef4 100644 --- a/src/app/components/CarouselCoins.tsx +++ b/src/app/components/CarouselCoins.tsx @@ -25,6 +25,10 @@ export default function CarouselCoins() { (state) => state.currency.currentCurrency.code ); + const currencySymbol = useAppSelector( + (state) => state.currency.currentCurrency.symbol + ); + useEffect(() => { dispatch(getCoinData(currencyCode)); }, [dispatch, currencyCode]); @@ -48,25 +52,20 @@ export default function CarouselCoins() { key={coin.id} className="sm:basis-1/2 md:basis-1/3 lg:basis-1/4 xl:basis-1/5" > -
handleCarousel(coin.id)} - className="h-[88px] dark:bg-[#191925] bg-[#FFFFFF] flex items-center rounded-lg cursor-pointer" + className="h-[88px] w-full dark:bg-[#191925] bg-[rgb(255,255,255)] flex items-center gap-4 rounded-lg cursor-pointer px-4" > -
- {coin.name} -
-
-

+ {coin.name} + +
+

{coin.name} ({coin.symbol.toUpperCase()})

- {coin.current_price} {currencyCode.toUpperCase()} + {currencySymbol} + {coin.current_price} {coin.price_change_percentage_24h > 0 ? ( @@ -84,7 +83,7 @@ export default function CarouselCoins() {
-

+ ))} diff --git a/src/app/components/CoinChart.tsx b/src/app/components/CoinChart.tsx new file mode 100644 index 0000000..0283532 --- /dev/null +++ b/src/app/components/CoinChart.tsx @@ -0,0 +1,148 @@ +"use client"; +import { useEffect, useState } from "react"; +import { useDispatch } from "react-redux"; +import { + BarChart, + Bar, + XAxis, + Tooltip, + ResponsiveContainer, + AreaChart, + Area, +} from "recharts"; +import { AppDispatch, useAppSelector } from "../../redux/store"; +import { getCoinDataGraph } from "../../redux/features/selectedCoinSlice"; +import formatNumber from "../utils/formatNumber"; + +type Payload = { + date: string; + value?: number; + volume?: number; +}; + +type CoinChartProps = { + chartType: "price" | "volume"; + currencyCode: string; + currencySymbol?: string; +}; + +export default function CoinChart({ + chartType, + currencyCode, + currencySymbol, +}: CoinChartProps) { + const dispatch: AppDispatch = useDispatch(); + const { selectedCoins, loading, hasError } = useAppSelector( + (state) => state.selectedCoin + ); + const [currentValue, setCurrentValue] = useState(""); + const [currentDate, setCurrentDate] = useState(""); + + useEffect(() => { + dispatch( + getCoinDataGraph({ currency: currencyCode, days: "3", coinId: "bitcoin" }) + ); + }, [dispatch, currencyCode]); + + useEffect(() => { + if (selectedCoins.length > 0) { + const lastDataPoint = + chartType === "price" + ? selectedCoins[0].prices[selectedCoins[0].prices.length - 1] + : selectedCoins[0].total_volumes[ + selectedCoins[0].total_volumes.length - 1 + ]; + const lastValue = lastDataPoint[1]; + const lastDate = new Date(lastDataPoint[0]).toLocaleDateString(); + + setCurrentValue(`${lastValue.toFixed(2)}`); + setCurrentDate(lastDate); + } + }, [selectedCoins, chartType, currencyCode]); + + if (loading === "pending") { + return
Loading...
; + } + + if (hasError) { + return
Error fetching data
; + } + + const coin = selectedCoins.length > 0 ? selectedCoins[0] : null; + + const formattedData = coin + ? chartType === "price" + ? coin.prices.map(([time, value]) => ({ + date: new Date(time).toLocaleDateString(), + value, + })) + : coin.total_volumes.map(([time, volume]) => ({ + date: new Date(time).toLocaleDateString(), + volume, + })) + : []; + + const handleMouseMove = (e: { activePayload?: { payload: Payload }[] }) => { + if (e.activePayload && e.activePayload.length > 0) { + const { payload } = e.activePayload[0]; + const dataKey = chartType === "price" ? "value" : "volume"; + const value = payload[dataKey]?.toFixed(2) ?? "0"; + setCurrentValue(value); + setCurrentDate(payload.date); + } + }; + + return ( +
+
+ + {chartType === "volume" + ? "Volume 24h" + : `${coin?.id.charAt(0).toUpperCase()}${coin?.id.slice(1)}`} + + + {currentValue + ? `${currencySymbol}${formatNumber(parseFloat(currentValue))}` + : "Fetching..."} + + + {currentDate} + +
+ + {chartType === "volume" ? ( + + + + + + + + + } /> + + + ) : ( + + + + + + + + + + + + } /> + + )} + +
+ ); +} diff --git a/src/app/components/GraphCoins.tsx b/src/app/components/GraphCoins.tsx index 52fd0aa..ca63c25 100644 --- a/src/app/components/GraphCoins.tsx +++ b/src/app/components/GraphCoins.tsx @@ -1,11 +1,26 @@ -import PriceChart from "./PriceChart"; -import VolumeChart from "./VolumeChart"; +"use client"; +import CoinChart from "./CoinChart"; +import { useAppSelector } from "../../redux/store"; export default function GraphCoins() { + const currencyCode = useAppSelector( + (state) => state.currency.currentCurrency.code + ); + const currencySimbol = useAppSelector( + (state) => state.currency.currentCurrency.symbol + ); return (
- - + +
); } diff --git a/src/app/components/PriceChart.tsx b/src/app/components/PriceChart.tsx deleted file mode 100644 index df78ee8..0000000 --- a/src/app/components/PriceChart.tsx +++ /dev/null @@ -1,110 +0,0 @@ -"use client"; -import { useDispatch } from "react-redux"; -import { useEffect, useState } from "react"; -import { XAxis, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts"; -import { AppDispatch, useAppSelector } from "../../redux/store"; -import { getCoinDataGraph } from "../../redux/features/selectedCoinSlice"; - -export default function PriceChart() { - const dispatch: AppDispatch = useDispatch(); - const { selectedCoins, loading, hasError } = useAppSelector( - (state) => state.selectedCoin - ); - - const currencyCode = useAppSelector( - (state) => state.currency.currentCurrency.code - ); - const currencySimbol = useAppSelector( - (state) => state.currency.currentCurrency.symbol - ); - - const [currentPriceCoin, setCurrentPriceCoin] = useState(""); - const [currentDate, setCurrentDate] = useState(""); - - useEffect(() => { - dispatch( - getCoinDataGraph({ - currency: currencyCode, - days: "3", - coinId: "bitcoin", - }) - ); - }, [dispatch, currencyCode]); - - useEffect(() => { - if (selectedCoins.length > 0) { - const lastDataPoint = - selectedCoins[0].prices[selectedCoins[0].prices.length - 1]; - const lastPrice = lastDataPoint[1]; - const lastDate = new Date(lastDataPoint[0]).toLocaleDateString(); - - setCurrentPriceCoin(`${lastPrice.toFixed(2)}`); - setCurrentDate(lastDate); - } - }, [selectedCoins, currencyCode]); - - if (loading === "pending") { - return
Loading...
; - } - - if (hasError) { - return
Error fetching data
; - } - - const coin = selectedCoins.length > 0 ? selectedCoins[0] : null; - - const formattedData = coin - ? coin.prices.map(([time, price]) => ({ - date: new Date(time).toLocaleDateString(), - price, - })) - : []; - - const handleMouseMove = (e: any) => { - if (e.activePayload && e.activePayload.length > 0) { - const { payload } = e.activePayload[0]; - setCurrentPriceCoin(`${payload.price.toFixed(2)}`); - setCurrentDate(payload.date); - } - }; - - return ( -
-
- - {coin - ? coin.id.charAt(0).toUpperCase() + coin.id.slice(1) - : "Loading..."} - - - {currentPriceCoin - ? `${currencySimbol}${currentPriceCoin}` - : "Fetching..."} - - - {currentDate} - -
- - - - - - - - - - - - - } /> - - -
- ); -} diff --git a/src/app/components/VolumeChart.tsx b/src/app/components/VolumeChart.tsx deleted file mode 100644 index bc41b7b..0000000 --- a/src/app/components/VolumeChart.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client"; -import { useDispatch } from "react-redux"; -import { useEffect, useState } from "react"; -import { BarChart, Bar, XAxis, Tooltip, ResponsiveContainer } from "recharts"; -import { AppDispatch, useAppSelector } from "../../redux/store"; -import { getCoinDataGraph } from "../../redux/features/selectedCoinSlice"; -import formatNumber from "../utils/formatNumber"; - -export default function VolumeChart() { - const dispatch: AppDispatch = useDispatch(); - const { selectedCoins, loading, hasError } = useAppSelector( - (state) => state.selectedCoin - ); - - const currencyCode = useAppSelector( - (state) => state.currency.currentCurrency.code - ); - const currencySimbol = useAppSelector( - (state) => state.currency.currentCurrency.symbol - ); - const [currentVolume, setCurrentVolume] = useState(""); - const [currentDateVolume, setCurrentDateVolume] = useState(""); - - useEffect(() => { - dispatch( - getCoinDataGraph({ - currency: currencyCode, - days: "3", - coinId: "bitcoin", - }) - ); - }, [dispatch, currencyCode]); - - useEffect(() => { - if (selectedCoins.length > 0) { - const lastDataPoint = - selectedCoins[0].total_volumes[ - selectedCoins[0].total_volumes.length - 1 - ]; - const lastTotalVolume = lastDataPoint[1]; - const lastDate = new Date(lastDataPoint[0]).toLocaleDateString(); - - setCurrentVolume(`${lastTotalVolume.toFixed(2)}`); - setCurrentDateVolume(lastDate); - } - }, [selectedCoins, currencyCode]); - - if (loading === "pending") { - return
Loading...
; - } - - if (hasError) { - return
Error fetching data
; - } - - const coin = selectedCoins.length > 0 ? selectedCoins[0] : null; - - const formattedDataVolume = coin - ? coin.total_volumes.map(([time, volume]) => ({ - date: new Date(time).toLocaleDateString(), - volume, - })) - : []; - - const handleMouseMoveOnVolume = (e: any) => { - if (e.activePayload && e.activePayload.length > 0) { - const { payload } = e.activePayload[0]; - setCurrentVolume(`${payload.volume.toFixed(2)}`); - setCurrentDateVolume(payload.date); - } - }; - - return ( -
-
- - Volume 24h - - - {currentVolume - ? `${currencySimbol}${formatNumber(parseFloat(currentVolume))}` - : "Fetching..."} - - - {currentDateVolume} - -
- - - - - - - - - - } /> - - - -
- ); -}