diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1e5c27b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,750 @@ +{ + "name": "github-contribution-chart", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-contribution-chart", + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@vercel/analytics": "^0.1.6", + "cheerio": "^1.0.0-rc.3", + "github-contributions-canvas": "^0.8.0", + "lodash": "^4.17.19", + "next": "^13.1.1", + "normalize.css": "^8.0.1", + "prop-types": "^15.7.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.0", + "react-icons": "^4.7.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@next/env": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz", + "integrity": "sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw==", + "license": "MIT" + }, + "node_modules/@next/swc-android-arm-eabi": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.1.tgz", + "integrity": "sha512-qnFCx1kT3JTWhWve4VkeWuZiyjG0b5T6J2iWuin74lORCupdrNukxkq9Pm+Z7PsatxuwVJMhjUoYz7H4cWzx2A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-android-arm64": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.1.tgz", + "integrity": "sha512-eCiZhTzjySubNqUnNkQCjU3Fh+ep3C6b5DCM5FKzsTH/3Gr/4Y7EiaPZKILbvnXmhWtKPIdcY6Zjx51t4VeTfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.1.tgz", + "integrity": "sha512-9zRJSSIwER5tu9ADDkPw5rIZ+Np44HTXpYMr0rkM656IvssowPxmhK0rTreC1gpUCYwFsRbxarUJnJsTWiutPg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.1.tgz", + "integrity": "sha512-qWr9qEn5nrnlhB0rtjSdR00RRZEtxg4EGvicIipqZWEyayPxhUu6NwKiG8wZiYZCLfJ5KWr66PGSNeDMGlNaiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-freebsd-x64": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.1.tgz", + "integrity": "sha512-UwP4w/NcQ7V/VJEj3tGVszgb4pyUCt3lzJfUhjDMUmQbzG9LDvgiZgAGMYH6L21MoyAATJQPDGiAMWAPKsmumA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm-gnueabihf": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.1.tgz", + "integrity": "sha512-CnsxmKHco9sosBs1XcvCXP845Db+Wx1G0qouV5+Gr+HT/ZlDYEWKoHVDgnJXLVEQzq4FmHddBNGbXvgqM1Gfkg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.1.tgz", + "integrity": "sha512-JfDq1eri5Dif+VDpTkONRd083780nsMCOKoFG87wA0sa4xL8LGcXIBAkUGIC1uVy9SMsr2scA9CySLD/i+Oqiw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.1.tgz", + "integrity": "sha512-GA67ZbDq2AW0CY07zzGt07M5b5Yaq5qUpFIoW3UFfjOPgb0Sqf3DAW7GtFMK1sF4ROHsRDMGQ9rnT0VM2dVfKA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.1.tgz", + "integrity": "sha512-nnjuBrbzvqaOJaV+XgT8/+lmXrSCOt1YYZn/irbDb2fR2QprL6Q7WJNgwsZNxiLSfLdv+2RJGGegBx9sLBEzGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.1.tgz", + "integrity": "sha512-CM9xnAQNIZ8zf/igbIT/i3xWbQZYaF397H+JroF5VMOCUleElaMdQLL5riJml8wUfPoN3dtfn2s4peSr3azz/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.1.tgz", + "integrity": "sha512-pzUHOGrbgfGgPlOMx9xk3QdPJoRPU+om84hqVoe6u+E0RdwOG0Ho/2UxCgDqmvpUrMab1Deltlt6RqcXFpnigQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.1.tgz", + "integrity": "sha512-WeX8kVS46aobM9a7Xr/kEPcrTyiwJqQv/tbw6nhJ4fH9xNZ+cEcyPoQkwPo570dCOLz3Zo9S2q0E6lJ/EAUOBg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.1.tgz", + "integrity": "sha512-mVF0/3/5QAc5EGVnb8ll31nNvf3BWpPY4pBb84tk+BfQglWLqc5AC9q1Ht/YMWiEgs8ALNKEQ3GQnbY0bJF2Gg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@vercel/analytics": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-0.1.6.tgz", + "integrity": "sha512-zNd5pj3iDvq8IMBQHa1YRcIteiw6ZiPB8AsONHd8ieFXlNpLqhXfIYnf4WvTfZ7S1NSJ++mIM14aJnNac/VMXQ==", + "license": "MPL-2.0", + "peerDependencies": { + "react": "^16.8||^17||^18" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001441", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz", + "integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.5", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.5.tgz", + "integrity": "sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==", + "license": "MIT", + "dependencies": { + "cheerio-select-tmp": "^0.1.0", + "dom-serializer": "~1.2.0", + "domhandler": "^4.0.0", + "entities": "~2.1.0", + "htmlparser2": "^6.0.0", + "parse5": "^6.0.0", + "parse5-htmlparser2-tree-adapter": "^6.0.0" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/cheerio-select-tmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz", + "integrity": "sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==", + "deprecated": "Use cheerio-select instead", + "license": "BSD-2-Clause", + "dependencies": { + "css-select": "^3.1.2", + "css-what": "^4.0.0", + "domelementtype": "^2.1.0", + "domhandler": "^4.0.0", + "domutils": "^2.4.4" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/css-select": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz", + "integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^4.0.0", + "domhandler": "^4.0.0", + "domutils": "^2.4.3", + "nth-check": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz", + "integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, + "node_modules/date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "license": "MIT", + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dom-serializer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz", + "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", + "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.1.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.5.0.tgz", + "integrity": "sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/github-contributions-canvas": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/github-contributions-canvas/-/github-contributions-canvas-0.8.0.tgz", + "integrity": "sha512-qFwTRW+qnLKjKaVUk4YsVCvh6ymBab1lt4wx2Tg/d7J/eeEaMnbWAsmjFNWs4mjJ1ptD4pqlUA8fUw2s/ZBMjg==", + "license": "MIT", + "dependencies": { + "date-fns": "^2.29.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/goober": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.12.tgz", + "integrity": "sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/htmlparser2": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.1.tgz", + "integrity": "sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.4.4", + "entities": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-13.1.1.tgz", + "integrity": "sha512-R5eBAaIa3X7LJeYvv1bMdGnAVF4fVToEjim7MkflceFPuANY3YyvFxXee/A+acrSYwYPvOvf7f6v/BM/48ea5w==", + "license": "MIT", + "dependencies": { + "@next/env": "13.1.1", + "@swc/helpers": "0.4.14", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=14.6.0" + }, + "optionalDependencies": { + "@next/swc-android-arm-eabi": "13.1.1", + "@next/swc-android-arm64": "13.1.1", + "@next/swc-darwin-arm64": "13.1.1", + "@next/swc-darwin-x64": "13.1.1", + "@next/swc-freebsd-x64": "13.1.1", + "@next/swc-linux-arm-gnueabihf": "13.1.1", + "@next/swc-linux-arm64-gnu": "13.1.1", + "@next/swc-linux-arm64-musl": "13.1.1", + "@next/swc-linux-x64-gnu": "13.1.1", + "@next/swc-linux-x64-musl": "13.1.1", + "@next/swc-win32-arm64-msvc": "13.1.1", + "@next/swc-win32-ia32-msvc": "13.1.1", + "@next/swc-win32-x64-msvc": "13.1.1" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^6.0.0 || ^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-hot-toast": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.0.tgz", + "integrity": "sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==", + "license": "MIT", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-icons": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz", + "integrity": "sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + } + } +} diff --git a/src/components/StatCard.js b/src/components/StatCard.js new file mode 100644 index 0000000..0cf1d67 --- /dev/null +++ b/src/components/StatCard.js @@ -0,0 +1,22 @@ +import React from "react"; +import PropTypes from "prop-types"; + +const StatCard = ({ icon, label, value, subtext }) => ( +
+
{icon}
+
+
{value}
+
{label}
+ {subtext &&
{subtext}
} +
+
+); + +StatCard.propTypes = { + icon: PropTypes.element.isRequired, + label: PropTypes.string.isRequired, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + subtext: PropTypes.string +}; + +export default StatCard; diff --git a/src/components/StatsDashboard.js b/src/components/StatsDashboard.js new file mode 100644 index 0000000..6a16006 --- /dev/null +++ b/src/components/StatsDashboard.js @@ -0,0 +1,91 @@ +import { useMemo, useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import { + TbSum, + TbFlame, + TbTrendingUp, + TbStar, + TbChartLine, + TbCalendarEvent, + TbCalendar, + TbArrowUpRight, + TbChartBar +} from "react-icons/tb"; +import StatCard from "./StatCard"; +import { calculateStats, formatNumber } from "../utils/stats"; + +const StatsDashboard = ({ data }) => { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + const stats = useMemo(() => calculateStats(data), [data]); + + if (!mounted) { + return null; + } + + return ( +
+

+ Contribution Statistics +

+
+ } + label="Total Contributions" + value={formatNumber(stats.total)} + /> + } + label="Longest Streak" + value={`${stats.longestStreak} days`} + /> + } + label="Current Streak" + value={`${stats.currentStreak} days`} + /> + } + label="Best Day" + value={`Level ${stats.bestDay.intensity}`} + subtext={stats.bestDay.date} + /> + } + label="Daily Average" + value={stats.dailyAverage} + /> + } + label="Busiest Day" + value={stats.busiestDayOfWeek} + /> + } + label="Busiest Month" + value={stats.busiestMonth} + /> + {stats.yearOverYear && ( + } + label="Year-over-Year" + value={stats.yearOverYear} + /> + )} +
+
+ ); +}; + +StatsDashboard.propTypes = { + data: PropTypes.shape({ + years: PropTypes.array.isRequired, + contributions: PropTypes.array.isRequired + }).isRequired +}; + +export default StatsDashboard; diff --git a/src/pages/index.js b/src/pages/index.js index 587de8c..01f2f1e 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -9,6 +9,7 @@ import { copyToClipboard } from "../utils/export"; import ThemeSelector from "../components/themes"; +import StatsDashboard from "../components/StatsDashboard"; const App = () => { const inputRef = useRef(); @@ -160,6 +161,8 @@ const App = () => { + + )} diff --git a/src/styles/App.css b/src/styles/App.css index 63b5702..8e5688a 100644 --- a/src/styles/App.css +++ b/src/styles/App.css @@ -285,3 +285,113 @@ footer a { flex-wrap: wrap; } } + +.StatsDashboard { + margin-top: 2rem; + margin-bottom: 2rem; + padding: 1.5rem; + border: 1px solid var(--input-border); + border-radius: 0.8rem; + background-color: var(--header-bg); + text-align: left; +} + +.StatsDashboard-title { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 0 0 1.5rem 0; + font-size: 1.4rem; + font-weight: 600; + color: var(--text); +} + +.StatsDashboard-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1rem; +} + +.StatCard { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + background-color: var(--bg); + border-radius: 0.6rem; + border: 1px solid var(--input-border); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.StatCard:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.StatCard-icon { + display: flex; + align-items: center; + justify-content: center; + width: 3rem; + height: 3rem; + border-radius: 50%; + background-color: var(--button-bg); + color: var(--button-text); + flex-shrink: 0; +} + +.StatCard-icon svg { + width: 1.5rem; + height: 1.5rem; +} + +.StatCard-content { + display: flex; + flex-direction: column; + min-width: 0; +} + +.StatCard-value { + font-size: 1.4rem; + font-weight: 700; + color: var(--input-text); + line-height: 1.2; +} + +.StatCard-label { + font-size: 0.9rem; + color: var(--text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.StatCard-subtext { + font-size: 0.85rem; + color: var(--text); + opacity: 0.7; +} + +@media all and (max-width: 970px) { + .StatsDashboard-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media all and (max-width: 500px) { + .StatsDashboard { + padding: 1rem; + } + + .StatsDashboard-grid { + grid-template-columns: 1fr; + } + + .StatCard { + padding: 0.8rem; + } + + .StatCard-value { + font-size: 1.2rem; + } +} diff --git a/src/utils/stats.js b/src/utils/stats.js new file mode 100644 index 0000000..d0ce27d --- /dev/null +++ b/src/utils/stats.js @@ -0,0 +1,233 @@ +function isConsecutiveDay(date1, date2) { + const d1 = new Date(date1); + const d2 = new Date(date2); + const diffTime = d2.getTime() - d1.getTime(); + const diffDays = diffTime / (1000 * 60 * 60 * 24); + return diffDays === 1; +} + +function formatDate(dateString) { + if (!dateString) return "N/A"; + const options = { year: "numeric", month: "short", day: "numeric" }; + return new Date(dateString).toLocaleDateString("en-US", options); +} + +export function formatNumber(num) { + return num.toLocaleString("en-US"); +} + +export function calculateTotal(years) { + if (!years || years.length === 0) return 0; + return years.reduce((sum, year) => sum + (year.total || 0), 0); +} + + +export function calculateLongestStreak(contributions) { + if (!contributions || contributions.length === 0) return 0; + + const sorted = [...contributions].sort((a, b) => + a.date.localeCompare(b.date) + ); + + let longestStreak = 0; + let currentStreak = 0; + let previousDate = null; + + for (const contrib of sorted) { + if (contrib.count > 0 || parseInt(contrib.intensity, 10) > 0) { + if (previousDate && isConsecutiveDay(previousDate, contrib.date)) { + currentStreak++; + } else { + currentStreak = 1; + } + longestStreak = Math.max(longestStreak, currentStreak); + previousDate = contrib.date; + } else { + currentStreak = 0; + previousDate = null; + } + } + + return longestStreak; +} + +export function calculateCurrentStreak(contributions) { + if (!contributions || contributions.length === 0) return 0; + + const today = new Date().toISOString().split("T")[0]; + let streak = 0; + let expectedDate = today; + + for (const contrib of contributions) { + if (contrib.date > today) continue; + + const hasContribution = + contrib.count > 0 || parseInt(contrib.intensity, 10) > 0; + + if (contrib.date === expectedDate) { + if (hasContribution) { + streak++; + const d = new Date(expectedDate); + d.setDate(d.getDate() - 1); + expectedDate = d.toISOString().split("T")[0]; + } else { + if (contrib.date === today) { + const d = new Date(expectedDate); + d.setDate(d.getDate() - 1); + expectedDate = d.toISOString().split("T")[0]; + continue; + } + break; + } + } else if (contrib.date < expectedDate) { + break; + } + } + + return streak; +} + +export function findBestDay(contributions) { + if (!contributions || contributions.length === 0) { + return { count: 0, date: "N/A" }; + } + + // Need to look at intensity since count is only populated on first day + // Group by date and find max intensity, but we want actual contribution count + // Since we don't have individual day counts, we'll use intensity as a proxy + const best = contributions.reduce( + (max, contrib) => { + const intensity = parseInt(contrib.intensity, 10) || 0; + if (intensity > max.intensity) { + return { intensity, date: contrib.date }; + } + return max; + }, + { intensity: 0, date: null } + ); + + return { + intensity: best.intensity, + date: formatDate(best.date) + }; +} + +export function calculateDailyAverage(years, contributions) { + if (!contributions || contributions.length === 0) return "0.0"; + if (!years || years.length === 0) return "0.0"; + + const total = calculateTotal(years); + const totalDays = contributions.length; + + if (totalDays === 0) return "0.0"; + return (total / totalDays).toFixed(1); +} + +export function findBusiestDayOfWeek(contributions) { + if (!contributions || contributions.length === 0) return "N/A"; + + const dayNames = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ]; + const dayCounts = [0, 0, 0, 0, 0, 0, 0]; + + for (const contrib of contributions) { + const dayIndex = new Date(contrib.date).getDay(); + const intensity = parseInt(contrib.intensity, 10) || 0; + dayCounts[dayIndex] += intensity; + } + + const maxCount = Math.max(...dayCounts); + if (maxCount === 0) return "N/A"; + + const busiestIndex = dayCounts.indexOf(maxCount); + return dayNames[busiestIndex]; +} + +export function findBusiestMonth(contributions) { + if (!contributions || contributions.length === 0) return "N/A"; + + const monthNames = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ]; + const monthCounts = Array(12).fill(0); + + for (const contrib of contributions) { + const monthIndex = new Date(contrib.date).getMonth(); + const intensity = parseInt(contrib.intensity, 10) || 0; + monthCounts[monthIndex] += intensity; + } + + const maxCount = Math.max(...monthCounts); + if (maxCount === 0) return "N/A"; + + const busiestIndex = monthCounts.indexOf(maxCount); + return monthNames[busiestIndex]; +} + + +export function calculateYearOverYear(years) { + if (!years || years.length < 2) return null; + + const currentYear = years[0]; + const previousYear = years[1]; + + if (!previousYear.total || previousYear.total === 0) return null; + + const change = + ((currentYear.total - previousYear.total) / previousYear.total) * 100; + const sign = change >= 0 ? "+" : ""; + + return `${sign}${change.toFixed(0)}%`; +} + + +function getEmptyStats() { + return { + total: 0, + longestStreak: 0, + currentStreak: 0, + bestDay: { intensity: 0, date: "N/A" }, + dailyAverage: "0.0", + busiestDayOfWeek: "N/A", + busiestMonth: "N/A", + yearOverYear: null + }; +} + + +export function calculateStats(data) { + if (!data || !data.contributions || data.contributions.length === 0) { + return getEmptyStats(); + } + + const { years, contributions } = data; + + return { + total: calculateTotal(years), + longestStreak: calculateLongestStreak(contributions), + currentStreak: calculateCurrentStreak(contributions), + bestDay: findBestDay(contributions), + dailyAverage: calculateDailyAverage(years, contributions), + busiestDayOfWeek: findBusiestDayOfWeek(contributions), + busiestMonth: findBusiestMonth(contributions), + yearOverYear: calculateYearOverYear(years) + }; +}