diff --git a/components/Developers.js b/components/Developers.js
index de71823..07cd10a 100644
--- a/components/Developers.js
+++ b/components/Developers.js
@@ -4,6 +4,8 @@ import { faWindows, faApple } from '@fortawesome/free-brands-svg-icons';
import Link from 'next/link';
import styles from './Developers.module.css';
import { getReleasesDownloadCount } from 'utils/githubStats';
+import DownloadStats from './DownloadStats';
+
export default function Developers() {
const [latestRelease, setLatestRelease] = useState({ version: null, date: null });
@@ -162,6 +164,7 @@ export default function Developers() {
style={{ border: '0', borderRadius: '6px' }}
className="mx-auto mb-4"
>
+
Don't forget to{' '}
Register for Updates.
diff --git a/components/DownloadStats.js b/components/DownloadStats.js
new file mode 100644
index 0000000..fa82b94
--- /dev/null
+++ b/components/DownloadStats.js
@@ -0,0 +1,91 @@
+import React, { useState, useEffect } from 'react';
+import { Line } from 'react-chartjs-2';
+import 'chart.js/auto';
+
+const DownloadStats = () => {
+ const [chartData, setChartData] = useState({
+ labels: [],
+ datasets: [
+ {
+ label: 'Daily Downloads',
+ data: [],
+ borderColor: 'rgb(75, 192, 192)',
+ backgroundColor: 'rgba(75, 192, 192, 0.5)',
+ },
+ {
+ label: 'Cumulative Total Downloads',
+ data: [],
+ borderColor: 'rgb(255, 99, 132)',
+ backgroundColor: 'rgba(255, 99, 132, 0.5)',
+ },
+ ],
+ });
+
+ const fetchReleaseData = async (page = 1) => {
+ const queryParams = new URLSearchParams({
+ per_page: 30,
+ page,
+ });
+ const response = await fetch(`https://api.github.com/repos/OpenAdaptAI/OpenAdapt/releases?${queryParams.toString()}`);
+ return response.json();
+ };
+
+ useEffect(() => {
+ let cumulativeTotalDownloads = 0;
+ let allReleases = [];
+
+ const processReleases = async (page = 1) => {
+ const releaseData = await fetchReleaseData(page);
+ if (releaseData.length === 0) return;
+
+ allReleases = [...allReleases, ...releaseData];
+ if (releaseData.length === 30) {
+ await processReleases(page + 1);
+ } else {
+ // Sort releases by published date
+ allReleases.sort((a, b) => new Date(a.published_at) - new Date(b.published_at));
+
+ const labels = [];
+ const dailyDownloads = [];
+ const cumulativeDownloads = [];
+
+ allReleases.forEach(release => {
+ const date = new Date(release.published_at).toLocaleDateString();
+ labels.push(date);
+
+ // Filter assets that are ZIP files and accumulate download counts
+ const dailyTotalDownloads = release.assets.reduce((acc, asset) => {
+ if (asset.name.endsWith('.zip')) {
+ return acc + asset.download_count;
+ }
+ return acc;
+ }, 0);
+
+ cumulativeTotalDownloads += dailyTotalDownloads;
+ dailyDownloads.push(dailyTotalDownloads);
+ cumulativeDownloads.push(cumulativeTotalDownloads);
+ });
+
+ setChartData({
+ labels,
+ datasets: [
+ { ...chartData.datasets[0], data: dailyDownloads },
+ { ...chartData.datasets[1], data: cumulativeDownloads },
+ ],
+ });
+ }
+ };
+
+ processReleases();
+ }, []);
+
+ return (
+
+
Download Statistics
+
+
+ );
+};
+
+export default DownloadStats;
+
diff --git a/package-lock.json b/package-lock.json
index d0c4897..7cb47c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,8 @@
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
+ "chart.js": "^4.4.3",
+ "chartjs-adapter-date-fns": "^3.0.0",
"cypress": "^10.0.3",
"daisyui": "^4.7.2",
"eslint-config-next": "^13.3.4",
@@ -20,6 +22,7 @@
"next": "^14.1.2",
"p5": "^1.9.1",
"react": "^18.2.0",
+ "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-fontawesome": "^1.7.1"
},
@@ -365,6 +368,11 @@
"node": ">= 8.0.0"
}
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ },
"node_modules/@next/env": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.2.tgz",
@@ -1665,6 +1673,26 @@
"node": ">=8"
}
},
+ "node_modules/chart.js": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz",
+ "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/chartjs-adapter-date-fns": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
+ "integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
+ "peerDependencies": {
+ "chart.js": ">=2.8.0",
+ "date-fns": ">=2.0.0"
+ }
+ },
"node_modules/check-more-types": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
@@ -2243,6 +2271,16 @@
"node": ">=0.10"
}
},
+ "node_modules/date-fns": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+ "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+ "peer": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/dayjs": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
@@ -6517,6 +6555,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-chartjs-2": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz",
+ "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -8380,6 +8427,11 @@
"vary": "^1.1.2"
}
},
+ "@kurkle/color": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ },
"@next/env": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.2.tgz",
@@ -9280,6 +9332,20 @@
}
}
},
+ "chart.js": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz",
+ "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==",
+ "requires": {
+ "@kurkle/color": "^0.3.0"
+ }
+ },
+ "chartjs-adapter-date-fns": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
+ "integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
+ "requires": {}
+ },
"check-more-types": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
@@ -9725,6 +9791,12 @@
"assert-plus": "^1.0.0"
}
},
+ "date-fns": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+ "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+ "peer": true
+ },
"dayjs": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
@@ -12816,6 +12888,12 @@
"loose-envify": "^1.1.0"
}
},
+ "react-chartjs-2": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz",
+ "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==",
+ "requires": {}
+ },
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
diff --git a/package.json b/package.json
index 57c7c3e..68cac34 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,8 @@
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
+ "chart.js": "^4.4.3",
+ "chartjs-adapter-date-fns": "^3.0.0",
"cypress": "^10.0.3",
"daisyui": "^4.7.2",
"eslint-config-next": "^13.3.4",
@@ -30,6 +32,7 @@
"next": "^14.1.2",
"p5": "^1.9.1",
"react": "^18.2.0",
+ "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-fontawesome": "^1.7.1"
},