From 1acd66be94d78d064e8ae1a41530159176376e1d Mon Sep 17 00:00:00 2001 From: codedbydollys10 Date: Tue, 26 May 2026 12:09:58 +0530 Subject: [PATCH 1/2] feat: add daily GitHub activity reminders and streak tracking --- src/components/DailyActivityStatus.tsx | 90 ++++++++++++ src/hooks/useGitHubData.ts | 191 +++++++++++++++++++++++++ src/pages/Tracker/Tracker.tsx | 5 + 3 files changed, 286 insertions(+) create mode 100644 src/components/DailyActivityStatus.tsx diff --git a/src/components/DailyActivityStatus.tsx b/src/components/DailyActivityStatus.tsx new file mode 100644 index 00000000..6804dcd7 --- /dev/null +++ b/src/components/DailyActivityStatus.tsx @@ -0,0 +1,90 @@ +import React from "react"; +import { Box, Paper, Typography, Stack, Chip, Alert, Divider } from "@mui/material"; + +interface DailyActivityStatusProps { + commitCountToday: number; + prOpenedToday: number; + prMergedToday: number; + issueActivityCountToday: number; + streakCount: number; + reminders: string[]; +} + +const DailyActivityStatus: React.FC = ({ + commitCountToday, + prOpenedToday, + prMergedToday, + issueActivityCountToday, + streakCount, + reminders, +}) => { + const summaryItems = [ + { + label: "Current streak", + value: `${streakCount} day${streakCount === 1 ? "" : "s"}`, + color: "primary", + }, + { + label: "Commits today", + value: `${commitCountToday}`, + color: commitCountToday > 0 ? "success" : "default", + }, + { + label: "PRs opened", + value: `${prOpenedToday}`, + color: prOpenedToday > 0 ? "success" : "default", + }, + { + label: "PRs merged", + value: `${prMergedToday}`, + color: prMergedToday > 0 ? "success" : "default", + }, + { + label: "Issue activity", + value: `${issueActivityCountToday}`, + color: issueActivityCountToday > 0 ? "success" : "default", + }, + ]; + + return ( + + + Daily Activity Status + + + + {summaryItems.map((item) => ( + + ))} + + + + + + {reminders.length > 0 ? ( + reminders.map((message, index) => ( + + {message} + + )) + ) : ( + + Great work today! Keep your contribution streak alive. + + )} + + + ); +}; + +export default DailyActivityStatus; diff --git a/src/hooks/useGitHubData.ts b/src/hooks/useGitHubData.ts index f4c78cf6..0ca953a3 100644 --- a/src/hooks/useGitHubData.ts +++ b/src/hooks/useGitHubData.ts @@ -21,6 +21,62 @@ interface FetchFilters { state?: string; } +interface DailyActivity { + commitCountToday: number; + prOpenedToday: number; + prMergedToday: number; + issueActivityCountToday: number; + streakCount: number; + reminders: string[]; +} + +const initialDailyActivity: DailyActivity = { + commitCountToday: 0, + prOpenedToday: 0, + prMergedToday: 0, + issueActivityCountToday: 0, + streakCount: 0, + reminders: [], +}; + +const getDateKey = (date: Date): string => + date.toISOString().split('T')[0]; + +const buildDailyReminders = ({ + commitCountToday, + prOpenedToday, + prMergedToday, + issueActivityCountToday, +}: DailyActivity): string[] => { + const reminders: string[] = []; + + if (commitCountToday === 0) { + reminders.push('πŸš€ You haven’t committed today'); + } + if (prOpenedToday === 0) { + reminders.push('πŸ’» No pull request opened today'); + } + if (prMergedToday === 0) { + reminders.push('🎯 No PR merged today'); + } + if (issueActivityCountToday === 0) { + reminders.push('πŸ“Œ Try contributing through issues'); + } + if ( + commitCountToday === 0 && + prOpenedToday === 0 && + prMergedToday === 0 && + issueActivityCountToday === 0 + ) { + reminders.push('πŸ”₯ Your contribution streak is at risk'); + } + if (reminders.length > 0) { + reminders.push('πŸ† Keep your streak alive'); + } + + return reminders; +}; + export const useGitHubData = ( getOctokit: () => Octokit | null ) => { @@ -31,6 +87,8 @@ export const useGitHubData = ( const [totalIssues, setTotalIssues] = useState(0); const [totalPrs, setTotalPrs] = useState(0); const [rateLimited, setRateLimited] = useState(false); + const [dailyActivity, setDailyActivity] = useState(initialDailyActivity); + const [dailyActivityLoaded, setDailyActivityLoaded] = useState(false); // Prevent stale responses overwriting latest data const lastRequestId = useRef(0); @@ -86,6 +144,135 @@ export const useGitHubData = ( }; }; + const getSearchCount = async ( + octokit: Octokit, + endpoint: 'GET /search/issues' | 'GET /search/commits', + q: string + ) => { + try { + const response = await octokit.request(endpoint, { + q, + per_page: 1, + headers: + endpoint === 'GET /search/commits' + ? { + accept: 'application/vnd.github.cloak-preview+json', + } + : undefined, + }); + + return response.data.total_count ?? 0; + } catch { + return 0; + } + }; + + const fetchStreakFromEvents = async ( + octokit: Octokit, + username: string + ) => { + try { + const response = await octokit.request('GET /users/{username}/events', { + username, + per_page: 100, + }); + + const eventDates = new Set(); + response.data.forEach((event: any) => { + const eventType = event.type; + const createdAt = event.created_at; + if ( + ['PushEvent', 'PullRequestEvent', 'IssuesEvent', + 'IssueCommentEvent', 'PullRequestReviewCommentEvent', + 'PullRequestReviewEvent'].includes(eventType) && + createdAt + ) { + eventDates.add(getDateKey(new Date(createdAt))); + } + }); + + let streak = 0; + for (let i = 0; i < 7; i += 1) { + const day = getDateKey( + new Date(Date.now() - i * 24 * 60 * 60 * 1000) + ); + if (eventDates.has(day)) { + streak += 1; + } else { + break; + } + } + + return streak; + } catch { + return 0; + } + }; + + const fetchDailyActivity = async ( + octokit: Octokit, + username: string, + requestId: number + ) => { + const today = getDateKey(new Date()); + + const [ + issueCreatedToday, + issueCommentedToday, + prOpenedToday, + prMergedToday, + ] = await Promise.all([ + getSearchCount( + octokit, + 'GET /search/issues', + `author:${username} type:issue created:>=${today}` + ), + getSearchCount( + octokit, + 'GET /search/issues', + `commenter:${username} type:issue updated:>=${today}` + ), + getSearchCount( + octokit, + 'GET /search/issues', + `author:${username} type:pr created:>=${today}` + ), + getSearchCount( + octokit, + 'GET /search/issues', + `author:${username} type:pr is:merged merged:>=${today}` + ), + ]); + + const commitCountToday = await getSearchCount( + octokit, + 'GET /search/commits', + `author:${username} author-date:>=${today}` + ); + + const issueActivityCountToday = issueCreatedToday + issueCommentedToday; + const streakCount = await fetchStreakFromEvents(octokit, username); + + if (requestId !== lastRequestId.current) { + return; + } + + setDailyActivity({ + commitCountToday, + prOpenedToday, + prMergedToday, + issueActivityCountToday, + streakCount, + reminders: buildDailyReminders({ + commitCountToday, + prOpenedToday, + prMergedToday, + issueActivityCountToday, + }), + }); + setDailyActivityLoaded(true); + }; + const fetchData = useCallback( async ( username: string, @@ -140,6 +327,8 @@ export const useGitHubData = ( ); } + requests.push(fetchDailyActivity(octokit, username, requestId)); + const results = await Promise.allSettled(requests); // Ignore stale requests @@ -247,6 +436,8 @@ export const useGitHubData = ( loading, error, rateLimited, + dailyActivity, + dailyActivityLoaded, fetchData, }; }; diff --git a/src/pages/Tracker/Tracker.tsx b/src/pages/Tracker/Tracker.tsx index 576f39bf..bb6d04cf 100644 --- a/src/pages/Tracker/Tracker.tsx +++ b/src/pages/Tracker/Tracker.tsx @@ -32,6 +32,7 @@ import { import { useTheme } from "@mui/material/styles"; import { useGitHubAuth } from "../../hooks/useGitHubAuth"; import { useGitHubData } from "../../hooks/useGitHubData"; +import DailyActivityStatus from "../../components/DailyActivityStatus"; import { KeyIcon } from "lucide-react"; const ROWS_PER_PAGE = 10; @@ -66,6 +67,8 @@ const Home: React.FC = () => { totalPrs, loading, error: dataError, + dailyActivity, + dailyActivityLoaded, fetchData, } = useGitHubData(getOctokit); @@ -242,6 +245,8 @@ const Home: React.FC = () => { + {dailyActivityLoaded && } + {/* Filters */} Date: Tue, 26 May 2026 13:05:04 +0530 Subject: [PATCH 2/2] fixed useGithubData.tsx as per the suggestion of coderabbitAi --- src/hooks/useGitHubData.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/hooks/useGitHubData.ts b/src/hooks/useGitHubData.ts index 0ca953a3..6e87b1c2 100644 --- a/src/hooks/useGitHubData.ts +++ b/src/hooks/useGitHubData.ts @@ -39,10 +39,16 @@ const initialDailyActivity: DailyActivity = { reminders: [], }; -const getDateKey = (date: Date): string => - date.toISOString().split('T')[0]; +const getDateKey = (date: Date): string => { + const year = date.getFullYear(); + const month = `${date.getMonth() + 1}`.padStart(2, '0'); + const day = `${date.getDate()}`.padStart(2, '0'); + + return `${year}-${month}-${day}`; +}; const buildDailyReminders = ({ + commitCountToday, prOpenedToday, prMergedToday,