diff --git a/package-lock.json b/package-lock.json
index eeb08e2..395786c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emailjs/browser": "^4.4.1",
"@tanstack/react-query": "^5.59.17",
+ "@vercel/analytics": "^1.4.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
@@ -24,6 +25,7 @@
"react-firebase-hooks": "^5.1.1",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.3.0",
+ "recharts": "^2.15.1",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
@@ -72,6 +74,17 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
+ "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@emailjs/browser": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@emailjs/browser/-/browser-4.4.1.tgz",
@@ -1164,6 +1177,60 @@
"@types/responselike": "^1.0.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.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
+ },
+ "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/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
@@ -1369,6 +1436,43 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
+ "node_modules/@vercel/analytics": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.4.1.tgz",
+ "integrity": "sha512-ekpL4ReX2TH3LnrRZTUKjHHNpNy9S1I7QmS+g/RQXoSUQ8ienzosuX7T9djZ/s8zPhBx1mpHP/Rw5875N+zQIQ==",
+ "peerDependencies": {
+ "@remix-run/react": "^2",
+ "@sveltejs/kit": "^1 || ^2",
+ "next": ">= 13",
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "svelte": ">= 4",
+ "vue": "^3",
+ "vue-router": "^4"
+ },
+ "peerDependenciesMeta": {
+ "@remix-run/react": {
+ "optional": true
+ },
+ "@sveltejs/kit": {
+ "optional": true
+ },
+ "next": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "svelte": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ },
+ "vue-router": {
+ "optional": true
+ }
+ }
+ },
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -2151,6 +2255,116 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"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",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -2242,6 +2456,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/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -2402,6 +2621,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",
@@ -3099,6 +3327,11 @@
"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/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -3124,6 +3357,14 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
+ "node_modules/fast-equals": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
+ "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -3848,6 +4089,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/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
@@ -4440,6 +4689,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.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@@ -5363,7 +5617,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",
@@ -5575,8 +5828,36 @@
"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-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "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 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "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",
@@ -5597,6 +5878,41 @@
"node": ">=8.10.0"
}
},
+ "node_modules/recharts": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
+ "integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "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 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.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/recharts/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
@@ -5618,6 +5934,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
@@ -6403,6 +6724,11 @@
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
},
+ "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",
@@ -6614,6 +6940,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.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "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/websocket-driver": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
diff --git a/package.json b/package.json
index 7d44ecd..0b84e8d 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@emailjs/browser": "^4.4.1",
"@tanstack/react-query": "^5.59.17",
+ "@vercel/analytics": "^1.4.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
@@ -25,6 +26,7 @@
"react-firebase-hooks": "^5.1.1",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.3.0",
+ "recharts": "^2.15.1",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
diff --git a/public/hero/h1.png b/public/hero/h1.png
new file mode 100644
index 0000000..84bd694
Binary files /dev/null and b/public/hero/h1.png differ
diff --git a/public/hero/h2.png b/public/hero/h2.png
new file mode 100644
index 0000000..4b77001
Binary files /dev/null and b/public/hero/h2.png differ
diff --git a/public/hero/h3.png b/public/hero/h3.png
new file mode 100644
index 0000000..b2c0010
Binary files /dev/null and b/public/hero/h3.png differ
diff --git a/public/hero/h4.png b/public/hero/h4.png
new file mode 100644
index 0000000..1e7dbc3
Binary files /dev/null and b/public/hero/h4.png differ
diff --git a/public/hero/h5.png b/public/hero/h5.png
new file mode 100644
index 0000000..7ea7853
Binary files /dev/null and b/public/hero/h5.png differ
diff --git a/public/hero/lights.png b/public/hero/lights.png
new file mode 100644
index 0000000..6f47c36
Binary files /dev/null and b/public/hero/lights.png differ
diff --git a/public/hero/mobo.svg b/public/hero/mobo.svg
new file mode 100644
index 0000000..50d5c4f
--- /dev/null
+++ b/public/hero/mobo.svg
@@ -0,0 +1,265 @@
+
diff --git a/public/landing/images/gbms.jpg b/public/landing/images/gbms.jpg
new file mode 100644
index 0000000..6b9e5eb
Binary files /dev/null and b/public/landing/images/gbms.jpg differ
diff --git a/public/landing/images/gbms.png b/public/landing/images/gbms.png
deleted file mode 100644
index 0f524ef..0000000
Binary files a/public/landing/images/gbms.png and /dev/null differ
diff --git a/public/landing/images/pc-builds.jpg b/public/landing/images/pc-builds.jpg
new file mode 100644
index 0000000..e7d85ef
Binary files /dev/null and b/public/landing/images/pc-builds.jpg differ
diff --git a/public/landing/images/pc-builds.png b/public/landing/images/pc-builds.png
deleted file mode 100644
index 95c77b2..0000000
Binary files a/public/landing/images/pc-builds.png and /dev/null differ
diff --git a/public/landing/images/socials.jpg b/public/landing/images/socials.jpg
new file mode 100644
index 0000000..13eb5f1
Binary files /dev/null and b/public/landing/images/socials.jpg differ
diff --git a/public/landing/images/socials.png b/public/landing/images/socials.png
deleted file mode 100644
index 566db94..0000000
Binary files a/public/landing/images/socials.png and /dev/null differ
diff --git a/public/landing/tube.png b/public/landing/tube.png
new file mode 100644
index 0000000..a0eb4ab
Binary files /dev/null and b/public/landing/tube.png differ
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index f2c80e6..9b5d9f6 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -35,7 +35,7 @@ export default function AboutPage() {
-
+
About Us
diff --git a/src/app/client-layout.tsx b/src/app/client-layout.tsx
index 365cd5e..026afe6 100644
--- a/src/app/client-layout.tsx
+++ b/src/app/client-layout.tsx
@@ -8,6 +8,7 @@ import { QueryClientProvider } from "@tanstack/react-query";
import { queryClient } from "@/lib/react-query/queryClient";
import { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
+import { trackPageView } from "@/lib/firebase/analytics";
export default function ClientLayout({
children,
@@ -22,13 +23,16 @@ export default function ClientLayout({
const isAdminRoute = pathname?.startsWith("/dashboard");
useEffect(() => {
- if(isAdminRoute) {
- document.body.style.backgroundColor = "#fff"
- }
- else {
- document.body.style.backgroundColor = "#080d14"
- }
- },[isAdminRoute])
+ if (isAdminRoute) {
+ document.body.style.backgroundColor = "#fff";
+ } else {
+ document.body.style.backgroundColor = "#080d14";
+ }
+ }, [isAdminRoute]);
+
+ useEffect(() => {
+ trackPageView(pathname);
+ }, [pathname]);
return (
diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx
new file mode 100644
index 0000000..e51d49b
--- /dev/null
+++ b/src/app/dashboard/analytics/page.tsx
@@ -0,0 +1,124 @@
+"use client";
+import { useEffect, useState } from "react";
+import {
+ collection,
+ query,
+ orderBy,
+ getDocs,
+ where,
+ Timestamp,
+ deleteDoc,
+} from "firebase/firestore";
+import { db } from "@/lib/firebase/firebase";
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+} from "recharts";
+
+interface PageViewData {
+ date: string;
+ views: number;
+}
+
+// AnalyticsDashboard.tsx
+interface WeeklyViewData {
+ weekLabel: string;
+ views: number;
+}
+
+export default function AnalyticsDashboard() {
+ const [weeklyData, setWeeklyData] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const fetchWeeklyViews = async () => {
+ const yearAgo = new Date();
+ yearAgo.setFullYear(yearAgo.getFullYear() - 1);
+
+ const q = query(
+ collection(db, "weeklyViews"),
+ where("timestamp", ">=", yearAgo),
+ orderBy("timestamp", "asc"),
+ );
+
+ const snapshot = await getDocs(q);
+ const viewsByWeek: { [key: string]: number } = {};
+
+ // Get unique week-year combinations
+ snapshot.forEach((doc) => {
+ const data = doc.data();
+ const key = `${data.year}-${data.weekNumber}`;
+ viewsByWeek[key] = (viewsByWeek[key] || 0) + 1;
+ });
+
+ // Sort by year and week
+ const formattedData = Object.entries(viewsByWeek)
+ .map(([key, views]) => {
+ const [year, week] = key.split("-");
+ return {
+ weekLabel: `W${week}-${year}`,
+ views,
+ sortKey: `${year}${week.padStart(2, "0")}`,
+ };
+ })
+ .sort((a, b) => a.sortKey.localeCompare(b.sortKey))
+ .map(({ weekLabel, views }) => ({ weekLabel, views }));
+
+ setWeeklyData(formattedData);
+ setLoading(false);
+ };
+
+ fetchWeeklyViews();
+ }, []);
+
+ if (loading)
+ return (
+ Loading...
+ );
+
+ const totalViews = weeklyData.reduce((sum, week) => sum + week.views, 0);
+ const avgViews = Math.round(totalViews / weeklyData.length);
+
+ return (
+
+
+
+
Total Views (in past year)
+
{totalViews}
+
+
+
+
Average Weekly Views
+
{avgViews}
+
+
+
+
+
Weekly Page Views
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index af7c427..29056ca 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { FaCalendarAlt, FaBoxOpen } from "react-icons/fa";
+import { FaCalendarAlt, FaBoxOpen, FaChartLine } from "react-icons/fa";
import { BsPcDisplay } from "react-icons/bs";
import Link from "next/link";
import ProtectedRoute from "@/components/admin/auth/ProtectedRoute";
@@ -15,7 +15,7 @@ export default function Dashboard() {
-
diff --git a/src/app/globals.css b/src/app/globals.css
index 89c8f74..1a67f4b 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,4 +1,5 @@
-@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Michroma&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap');
+
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -15,7 +16,7 @@ textarea {
}
body {
- font-family: "Inter", sans-serif;
+ font-family: "Space Grotesk", serif !important;
background-color: #ffffff;
overflow-x: hidden;
}
@@ -73,4 +74,27 @@ body {
.grad {
background: linear-gradient(0deg, #4255f922, #080d14);
filter: blur(20px);
+}
+
+
+/* monospace font - will update later */
+
+.m {
+ font-family: "Space Mono", serif;
+}
+
+/* fade background of hero */
+
+
+.hero-btn:hover {
+ /* Apply a full (all-around) shadow with #79C7FD */
+ box-shadow: 0 0 20px 5px rgba(121, 199, 253, 0.7);
+}
+
+.hero-fade {
+ background: linear-gradient(180deg,#080d1400 0%, #080d14 100%);
+}
+
+.about-fade {
+ background: linear-gradient(0deg,#080d1400 0%, #080d14 100%);
}
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 649ecea..13708ca 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,15 +1,18 @@
"use client";
import Footer from "@/components/Footer";
+import About from "@/components/landing/About";
+import Events from "@/components/landing/Events";
+import Faq from "@/components/landing/Faq";
import Hero from "@/components/landing/Hero";
-import Landing from "@/components/landing/Landing";
+
const Website = () => {
return (
-
- {/* Applied a noise bg to the page for added depth */}
-
+
);
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 6b08295..be4c7af 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -81,15 +81,15 @@ export default function Footer() {
rights reserved.
-
-
+
+
Gator User Design{" "}
{" "}
diff --git a/src/components/MemberCounts/SocialStats.tsx b/src/components/MemberCounts/SocialStats.tsx
index a5a7d71..357ae0b 100644
--- a/src/components/MemberCounts/SocialStats.tsx
+++ b/src/components/MemberCounts/SocialStats.tsx
@@ -54,7 +54,7 @@ const StatCard: React.FC = ({
{label}
-
+
{isLoading
? "Loading..."
: error
diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx
index ac8b863..1ab937f 100644
--- a/src/components/Navbar/Navbar.tsx
+++ b/src/components/Navbar/Navbar.tsx
@@ -17,7 +17,7 @@ export default function Nav() {
if (isAdminRoute) {
return (
<>
-
+
-
{isMobileAdminNavOpen && (
-
-
setIsMobileAdminNavOpen(false)} className="text-4xl absolute top-4 right-4">
-
-
setIsMobileAdminNavOpen(false)}>Home
-
setIsMobileAdminNavOpen(false)}>Projects
-
setIsMobileAdminNavOpen(false)}>Events
-
setIsMobileAdminNavOpen(false)}>About
+
+ setIsMobileAdminNavOpen(false)}
+ className="absolute right-4 top-4 text-4xl"
+ >
+
+
+
+ setIsMobileAdminNavOpen(false)}>
+ Home
+
+ setIsMobileAdminNavOpen(false)}
+ >
+ Projects
+
+ setIsMobileAdminNavOpen(false)}
+ >
+ Events
+
+ setIsMobileAdminNavOpen(false)}
+ >
+ About
+
)}
@@ -54,8 +79,8 @@ export default function Nav() {
}
return (
-