From 5a1e5e982bbe5330f9d69b2c85d44bd0529a6ed0 Mon Sep 17 00:00:00 2001 From: sanjay-kv Date: Wed, 25 Jun 2025 23:48:37 +1000 Subject: [PATCH 1/5] updated Sponsor list Signed-off-by: yashksaini-coder --- src/database/sponsors/index.tsx | 34 ++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/database/sponsors/index.tsx b/src/database/sponsors/index.tsx index d2fc3257..f6ec83c5 100644 --- a/src/database/sponsors/index.tsx +++ b/src/database/sponsors/index.tsx @@ -18,12 +18,36 @@ const sponsors: Sponsor[] = [ github: "https://github.com/arataka1313", linkedin: "https://www.linkedin.com/in/takashi-arai-2b697b360/", }, + { + name: "Sumit Khanna", + image: "https://avatars.githubusercontent.com/u/29749122?v=4", + description: "Developer at slickspender", + github: "https://github.com/jjackofall", + twitter: "https://x.com/jjackofall", + }, + { + name: "Prakhar Saxena", + image: "https://avatars.githubusercontent.com/u/111764688?v=4", + description: "Studying @McMaster University", + github: "https://www.linkedin.com/in/prakharsaxena5/", + linkedin: "https://www.linkedin.com/in/prakharsaxena5/", + }, + + { + name: "Varghese Baby", + image: "https://avatars.githubusercontent.com/u/68038057?v=4", + description: "Software Developer", + github: "https://github.com/varghese25", + linkedin: "https://www.linkedin.com/in/varghese-baby-138429175/", + }, + { name: "Carlos Romero", image: "https://avatars.githubusercontent.com/u/119690391?v=4", description: "CS Student of La Salle", github: "https://github.com/carlosromerorodriguez", linkedin: "https://www.linkedin.com/in/carlos-romero-rdgz/", + isPastSponsor: true, }, { name: "Yahya Banouk", @@ -32,8 +56,8 @@ const sponsors: Sponsor[] = [ github: "https://github.com/yahya-banouk", linkedin: "https://x.com/Yahya_Banouk", twitter: "https://x.com/Yahya_Banouk", - }, - + isPastSponsor: true, + }, { name: "Mike ", image: "https://avatars.githubusercontent.com/u/79911993?v=4", @@ -135,7 +159,7 @@ const sponsors: Sponsor[] = [ { name: "Monojit Pal", image: "https://avatars.githubusercontent.com/u/144504389?v=4", - description: "Student at St. Thomas' College of Engineering", + description: "Student at St. Thomas' College", github: "https://github.com/Monojit-Pal", linkedin: "https://www.linkedin.com/in/pal-monojit/", twitter: "https://x.com/Monojit__18", @@ -151,7 +175,7 @@ const sponsors: Sponsor[] = [ isPastSponsor: true, }, { - name: "Dhrubaraj Pati", + name: "Renato Maynard", image: "https://avatars.githubusercontent.com/u/79546214?v=4", description: "M.Sc. in Operations Research", github: "https://www.linkedin.com/in/renatomaynardetche/", @@ -160,7 +184,7 @@ const sponsors: Sponsor[] = [ isWeSponsor: true, }, { - name: "Renato Maynard", + name: "Dhrubaraj Pati", image: "https://avatars.githubusercontent.com/u/146111647?v=4", description: "Student at SVU", github: "https://github.com/codewithdhruba01", From 6d536b2a850c6039a0c50483415d44f0f43e4b1e Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Thu, 26 Jun 2025 18:38:26 +0530 Subject: [PATCH 2/5] feat(app): 126 Clickable cards, with real-time stats of RecodeHive Org Signed-off-by: yashksaini-coder --- src/components/Community/LandingCommunity.css | 80 ++++- src/components/Community/index.tsx | 144 +++++--- src/lib/statsProvider.tsx | 100 ++++-- src/pages/index.tsx | 2 +- src/services/githubService.ts | 329 ++++++++++++++++++ 5 files changed, 568 insertions(+), 87 deletions(-) create mode 100644 src/services/githubService.ts diff --git a/src/components/Community/LandingCommunity.css b/src/components/Community/LandingCommunity.css index 12efcffd..ff00248d 100644 --- a/src/components/Community/LandingCommunity.css +++ b/src/components/Community/LandingCommunity.css @@ -10,6 +10,7 @@ max-width: 100%; margin-bottom: 1rem; padding: auto 1rem; + flex-wrap: wrap; } .landing-community .landing-community__header .landing-community__title { @@ -26,6 +27,12 @@ text-shadow: 0 0 1px var(--ifm-color-primary); } +.landing-community .landing-community__header .landing-community__error { + color: var(--ifm-color-warning); + font-size: 0.9rem; + margin-top: 0.5rem; +} + .landing-community .landing-community__content { display: grid; grid-template-columns: 1fr 1fr; @@ -48,6 +55,23 @@ border-radius: 1rem; background-color: var(--ifm-color-background); box-shadow: 0 0 1px var(--ifm-color-primary); + transition: all 0.3s ease; + position: relative; +} + +.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable { + cursor: pointer; +} + +.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable:hover, +.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable:focus { + transform: scale(1.02); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + outline: none; +} + +.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.loading { + opacity: 0.7; } .landing-community .landing-community__content .landing-community__stats .landing-community__stat-item:hover { @@ -62,6 +86,34 @@ color: var(--ifm-color-primary); text-shadow: 0 0 1px var(--ifm-color-primary); padding-bottom: 0.5rem; + position: relative; +} + +.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item .landing-community__loading { + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; +} + +.landing-community .loading-spinner { + animation: spin 2s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.landing-community .external-link-icon { + font-size: 0.8em; + margin-left: 0.3rem; + opacity: 0.7; + transition: opacity 0.2s ease; +} + +.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable:hover .external-link-icon { + opacity: 1; } .landing-community .landing-community__content .landing-community__stats .landing-community__stat-item .landing-community__stat-description { @@ -75,12 +127,19 @@ border-radius: 1rem; background-color: var(--ifm-color-background); box-shadow: 0 0 1px var(--ifm-color-primary); + transition: all 0.3s ease; + position: relative; } -.landing-community .landing-community__content .landing-community__info:hover { +.landing-community .landing-community__content .landing-community__info.clickable { cursor: pointer; - transform: scale(1.01); - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); +} + +.landing-community .landing-community__content .landing-community__info.clickable:hover, +.landing-community .landing-community__content .landing-community__info.clickable:focus { + transform: scale(1.01); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + outline: none; } .landing-community .landing-community__content .landing-community__info .landing-community__image { @@ -100,12 +159,27 @@ color: var(--ifm-color-primary); text-shadow: 0 0 1px var(--ifm-color-primary); text-decoration: none; + font-weight: 600; } .landing-community .landing-community__content .landing-community__info .landing-community__info-text .landing-community__link:hover { text-decoration: underline; } +.landing-community .external-link-indicator { + display: flex; + align-items: center; + justify-content: center; + margin-top: 0.5rem; + gap: 0.5rem; + opacity: 0.7; + transition: opacity 0.2s ease; +} + +.landing-community .landing-community__content .landing-community__info.clickable:hover .external-link-indicator { + opacity: 1; +} + @media screen and (max-width: 768px) { .landing-community .landing-community__content { grid-template-columns: 1fr; diff --git a/src/components/Community/index.tsx b/src/components/Community/index.tsx index b4b7222a..40946ef6 100644 --- a/src/components/Community/index.tsx +++ b/src/components/Community/index.tsx @@ -7,7 +7,15 @@ type Props = { }; export const LandingCommunity: FC = ({ className }) => { - const { githubStarCountText, githubContributorsCount, githubForksCount } = useCommunityStatsContext(); + const { + githubStarCountText, + githubContributorsCountText, + githubForksCountText, + githubReposCountText, + loading, + error + } = useCommunityStatsContext(); + const [state, setState] = useState({ stat0: 0, stat1: 0, @@ -18,28 +26,29 @@ export const LandingCommunity: FC = ({ className }) => { const generateList = useMemo(() => [ { stat: githubStarCountText, - description: "Stars on our GitHub repository, showcase the support and contribution, we recieved from the community.", + description: "Stars across all our GitHub repositories, showcasing the support and appreciation from the community.", href: "https://github.com/recodehive", - // https://github.com/CodeHarborHub/codeharborhub.github.io/stargazers + label: "GitHub Stars" }, { - stat: 20, - description: "Live projects on recodehive, demonstrating the power of open-source collaboration.", + stat: githubReposCountText, + description: "Live public projects on RecodHive, demonstrating the power of open-source collaboration.", + href: "https://github.com/orgs/recodehive/repositories?q=visibility%3Apublic+archived%3Afalse", + label: "Public Repositories" }, { - stat: githubContributorsCount, - description: "List of Contributors who have made our repository better.", - href: "https://github.com/recodehive", - // https://github.com/CodeHarborHub/codeharborhub.github.io/graphs/contributors + stat: githubContributorsCountText, + description: "Amazing contributors who have made our repositories better and helped build our community.", + href: "https://github.com/orgs/recodehive/people", + label: "Contributors" }, { - stat: githubForksCount, - description: "Forks of our repository, showing how our community extends our work.", - href: "https://github.com/recodehive", - - //https://github.com/CodeHarborHub/codeharborhub.github.io/network/members + stat: githubForksCountText, + description: "Forks of our repositories, showing how our community extends and builds upon our work.", + href: "https://github.com/orgs/recodehive/discussions", + label: "Community Forks" }, - ], [githubStarCountText, githubContributorsCount, githubForksCount]); + ], [githubStarCountText, githubReposCountText, githubContributorsCountText, githubForksCountText]); const handleDynamicChange = (target: number, index: number) => { let count = 0; @@ -54,11 +63,22 @@ export const LandingCommunity: FC = ({ className }) => { }, 20); }; + const handleCardClick = (href: string) => { + if (href) { + window.open(href, '_blank', 'noopener,noreferrer'); + } + }; + useEffect(() => { - generateList.forEach((item, index) => { - handleDynamicChange(Number(item.stat), index); - }); - }, [generateList]); + if (!loading) { + generateList.forEach((item, index) => { + const numericStat = typeof item.stat === 'string' ? + parseInt(item.stat.replace(/[^\d]/g, '')) || 0 : + Number(item.stat); + handleDynamicChange(numericStat, index); + }); + } + }, [generateList, loading]); return (
@@ -67,33 +87,62 @@ export const LandingCommunity: FC = ({ className }) => { Discover the strength of our{" "} amazing community. + {error && ( +
+ ⚠️ Stats may be cached or incomplete +
+ )}
{generateList.map((item, index) => ( - +
handleCardClick(item.href)} + role={item.href ? "button" : "presentation"} + tabIndex={item.href ? 0 : -1} + onKeyDown={(e) => { + if (item.href && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault(); + handleCardClick(item.href); + } + }} + title={item.href ? `Click to visit ${item.label}` : item.label} + >
- {item.href ? ( - - {`${state[`stat${index}`]}${index !== 1 ? "" : ""}`} - + {loading ? ( +
+ +
) : ( - `${state[`stat${index}`]}` + + {item.stat} + {item.href && } + )}
{item.description}
- +
))}
-
+
handleCardClick("https://github.com/recodehive")} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleCardClick("https://github.com/recodehive"); + } + }} + title="Click to visit RecodHive GitHub Organization" + > = ({ className }) => { loading="lazy" />
- Our developers are the core of Hive community. We take pride in + Our developers are the core of RecodHive community. We take pride in our{" "} - - GitHub community - {" "} - with over{" "} - - 500+ contributors - {" "} - powering recodehive. + + GitHub organization + {" "} + with amazing{" "} + + contributors and maintainers + {" "} + powering RecodHive's growth. +
+ + Click to explore our GitHub +
diff --git a/src/lib/statsProvider.tsx b/src/lib/statsProvider.tsx index 6a31f0b9..e5b8bfa9 100644 --- a/src/lib/statsProvider.tsx +++ b/src/lib/statsProvider.tsx @@ -7,6 +7,7 @@ import React, { useMemo, useState, } from "react"; +import { githubService, type GitHubOrgStats } from "../services/githubService"; interface ICommunityStatsContext { githubStarCount: number; @@ -15,8 +16,15 @@ import React, { githubContributorsCountText: string; githubForksCount: number; githubForksCountText: string; + githubReposCount: number; + githubReposCountText: string; + githubDiscussionsCount: number; + githubDiscussionsCountText: string; loading: boolean; + error: string | null; + lastUpdated: Date | null; refetch: (signal: AbortSignal) => Promise; + clearCache: () => void; } export const CommunityStatsContext = createContext(undefined); @@ -27,61 +35,82 @@ import React, { export function CommunityStatsProvider({ children }: CommunityStatsProviderProps) { const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); const [githubStarCount, setGithubStarCount] = useState(0); const [githubContributorsCount, setGithubContributorsCount] = useState(0); const [githubForksCount, setGithubForksCount] = useState(0); - - const fetchGithubCount = useCallback(async (signal: AbortSignal) => { + const [githubReposCount, setGithubReposCount] = useState(0); + const [githubDiscussionsCount, setGithubDiscussionsCount] = useState(0); + const [lastUpdated, setLastUpdated] = useState(null); + + const fetchGithubStats = useCallback(async (signal: AbortSignal) => { try { setLoading(true); - - const response = await fetch( - "https://api.github.com/repos/CodeHarborHub/codeharborhub.github.io", - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - signal, - } - ); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + setError(null); + + const stats: GitHubOrgStats = await githubService.fetchOrganizationStats(signal); + + setGithubStarCount(stats.totalStars); + setGithubContributorsCount(stats.totalContributors); + setGithubForksCount(stats.totalForks); + setGithubReposCount(stats.publicRepositories); + setGithubDiscussionsCount(stats.discussionsCount); + setLastUpdated(new Date(stats.lastUpdated)); + + console.log("GitHub organization stats fetched successfully:", stats); + } catch (err) { + if (err.name !== 'AbortError') { + console.error("Error fetching GitHub organization stats:", err); + setError(err instanceof Error ? err.message : 'Failed to fetch GitHub stats'); + + // Set fallback values on error + setGithubStarCount(0); + setGithubContributorsCount(140); + setGithubForksCount(0); + setGithubReposCount(20); + setGithubDiscussionsCount(0); } - - const json = await response.json(); - setGithubStarCount(json.stargazers_count || 0); - setGithubContributorsCount(140); // Assuming this property exists - setGithubForksCount(json.forks_count || 0); - } catch (error) { - console.error("Error fetching GitHub data:", error); } finally { setLoading(false); } }, []); - + + const clearCache = useCallback(() => { + githubService.clearCache(); + // Optionally refetch after clearing cache + const abortController = new AbortController(); + fetchGithubStats(abortController.signal); + }, [fetchGithubStats]); + useEffect(() => { const abortController = new AbortController(); - fetchGithubCount(abortController.signal); - + fetchGithubStats(abortController.signal); + return () => { abortController.abort(); }; - }, [fetchGithubCount]); - + }, [fetchGithubStats]); + const githubStarCountText = useMemo(() => { return convertStatToText(githubStarCount); }, [githubStarCount]); - + const githubContributorsCountText = useMemo(() => { return convertStatToText(githubContributorsCount); }, [githubContributorsCount]); - + const githubForksCountText = useMemo(() => { return convertStatToText(githubForksCount); }, [githubForksCount]); - + + const githubReposCountText = useMemo(() => { + return convertStatToText(githubReposCount); + }, [githubReposCount]); + + const githubDiscussionsCountText = useMemo(() => { + return convertStatToText(githubDiscussionsCount); + }, [githubDiscussionsCount]); + const value: ICommunityStatsContext = { githubStarCount, githubStarCountText, @@ -89,8 +118,15 @@ import React, { githubContributorsCountText, githubForksCount, githubForksCountText, + githubReposCount, + githubReposCountText, + githubDiscussionsCount, + githubDiscussionsCountText, loading, - refetch: fetchGithubCount, + error, + lastUpdated, + refetch: fetchGithubStats, + clearCache, }; return ( diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 14178a81..b93e1cdc 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -81,7 +81,7 @@ export default function Home(): ReactNode {
- +
diff --git a/src/services/githubService.ts b/src/services/githubService.ts new file mode 100644 index 00000000..666196ec --- /dev/null +++ b/src/services/githubService.ts @@ -0,0 +1,329 @@ +// GitHub API service for fetching organization metrics +// Uses localStorage for caching to reduce API calls + +export interface GitHubOrgStats { + totalStars: number; + totalForks: number; + totalRepositories: number; + totalContributors: number; + publicRepositories: number; + discussionsCount: number; + lastUpdated: number; +} + +export interface GitHubRepository { + id: number; + name: string; + full_name: string; + stargazers_count: number; + forks_count: number; + contributors_url: string; + archived: boolean; + private: boolean; +} + +export interface GitHubOrganization { + login: string; + id: number; + public_repos: number; + followers: number; + following: number; +} + +class GitHubService { + private readonly ORG_NAME = 'recodehive'; + private readonly CACHE_KEY = 'github_org_stats'; + private readonly CACHE_DURATION = 30 * 60 * 1000; // 30 minutes in milliseconds + private readonly BASE_URL = 'https://api.github.com'; + + // Get headers for GitHub API requests + private getHeaders(): Record { + const headers: Record = { + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }; + + // Add GitHub token if available in environment + // Note: In production, you might want to use a server-side proxy to avoid exposing tokens + if (typeof window !== 'undefined' && (window as any).GITHUB_TOKEN) { + headers['Authorization'] = `token ${(window as any).GITHUB_TOKEN}`; + } + + return headers; + } + + // Fetch with error handling and rate limit consideration + private async fetchWithRetry(url: string, retries = 3): Promise { + for (let i = 0; i < retries; i++) { + try { + const response = await fetch(url, { + headers: this.getHeaders(), + }); + + if (response.status === 403) { + // Rate limited, wait a bit + const resetTime = response.headers.get('X-RateLimit-Reset'); + if (resetTime) { + const waitTime = Math.max(0, parseInt(resetTime) * 1000 - Date.now()); + if (waitTime < 60000) { // Only wait if less than 1 minute + await new Promise(resolve => setTimeout(resolve, Math.min(waitTime, 5000))); + continue; + } + } + } + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + return response; + } catch (error) { + if (i === retries - 1) throw error; + await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); + } + } + throw new Error('Failed after retries'); + } + + // Get cached data if valid + private getCachedData(): GitHubOrgStats | null { + if (typeof window === 'undefined') return null; + + try { + const cached = localStorage.getItem(this.CACHE_KEY); + if (!cached) return null; + + const data = JSON.parse(cached) as GitHubOrgStats; + const now = Date.now(); + + if (now - data.lastUpdated < this.CACHE_DURATION) { + return data; + } + } catch (error) { + console.warn('Error reading GitHub stats cache:', error); + // Clear invalid cache + localStorage.removeItem(this.CACHE_KEY); + } + + return null; + } + + // Cache data to localStorage + private setCachedData(data: GitHubOrgStats): void { + if (typeof window === 'undefined') return; + + try { + localStorage.setItem(this.CACHE_KEY, JSON.stringify({ + ...data, + lastUpdated: Date.now() + })); + } catch (error) { + console.warn('Error caching GitHub stats:', error); + } + } + + // Fetch organization basic info + private async fetchOrganizationInfo(signal?: AbortSignal): Promise { + const response = await fetch(`${this.BASE_URL}/orgs/${this.ORG_NAME}`, { + headers: this.getHeaders(), + signal, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch organization info: ${response.status}`); + } + + return response.json(); + } + + // Fetch all public repositories for the organization + private async fetchAllRepositories(signal?: AbortSignal): Promise { + const repositories: GitHubRepository[] = []; + let page = 1; + const perPage = 100; + + while (true) { + const response = await fetch( + `${this.BASE_URL}/orgs/${this.ORG_NAME}/repos?type=public&per_page=${perPage}&page=${page}&sort=updated`, + { + headers: this.getHeaders(), + signal, + } + ); + + if (!response.ok) { + throw new Error(`Failed to fetch repositories: ${response.status}`); + } + + const repos: GitHubRepository[] = await response.json(); + + if (repos.length === 0) break; + + repositories.push(...repos); + + if (repos.length < perPage) break; + + page++; + } + + return repositories; + } + + // Estimate contributors count (GitHub API doesn't provide org-wide contributor count) + private async estimateContributors(repositories: GitHubRepository[], signal?: AbortSignal): Promise { + // For performance, we'll sample top repositories by stars/activity + const topRepos = repositories + .filter(repo => !repo.archived && repo.stargazers_count > 0) + .sort((a, b) => b.stargazers_count - a.stargazers_count) + .slice(0, 10); // Sample top 10 repositories + + let totalContributors = 0; + + // Use parallel requests for better performance + const contributorPromises = topRepos.map(async (repo) => { + try { + const response = await fetch( + `${this.BASE_URL}/repos/${repo.full_name}/contributors?per_page=1`, + { + headers: this.getHeaders(), + signal, + } + ); + + if (response.ok) { + // Get total count from Link header if available + const linkHeader = response.headers.get('Link'); + if (linkHeader) { + const match = linkHeader.match(/page=(\d+)>; rel="last"/); + if (match) { + return parseInt(match[1]); + } + } + + // Fallback: count actual contributors + const contributors = await response.json(); + return Array.isArray(contributors) ? contributors.length : 0; + } + return 0; + } catch (error) { + console.warn(`Error fetching contributors for ${repo.name}:`, error); + return 0; + } + }); + + const contributorCounts = await Promise.all(contributorPromises); + + // Estimate total unique contributors (with some overlap factor) + const sumContributors = contributorCounts.reduce((sum, count) => sum + count, 0); + + // Apply estimation factor for unique contributors across repos + totalContributors = Math.round(sumContributors * 0.7); // Assume 30% overlap + + // Ensure minimum reasonable number + return Math.max(totalContributors, 140); + } + + // Get discussions count (approximate using search) + private async getDiscussionsCount(signal?: AbortSignal): Promise { + try { + const response = await fetch( + `${this.BASE_URL}/search/issues?q=repo:${this.ORG_NAME}/Support+type:issue`, + { + headers: this.getHeaders(), + signal, + } + ); + + if (response.ok) { + const data = await response.json(); + return data.total_count || 0; + } + } catch (error) { + console.warn('Error fetching discussions count:', error); + } + + return 0; + } + + // Main method to fetch all organization statistics + async fetchOrganizationStats(signal?: AbortSignal): Promise { + // Try to get cached data first + const cached = this.getCachedData(); + if (cached) { + return cached; + } + + try { + // Fetch organization info and repositories in parallel + const [orgInfo, repositories] = await Promise.all([ + this.fetchOrganizationInfo(signal), + this.fetchAllRepositories(signal), + ]); + + // Filter out archived repositories for active stats + const activeRepos = repositories.filter(repo => !repo.archived); + + // Calculate totals + const totalStars = repositories.reduce((sum, repo) => sum + repo.stargazers_count, 0); + const totalForks = repositories.reduce((sum, repo) => sum + repo.forks_count, 0); + + // Estimate contributors and get discussions count + const [totalContributors, discussionsCount] = await Promise.all([ + this.estimateContributors(activeRepos, signal), + this.getDiscussionsCount(signal), + ]); + + const stats: GitHubOrgStats = { + totalStars, + totalForks, + totalRepositories: repositories.length, + publicRepositories: activeRepos.length, + totalContributors, + discussionsCount, + lastUpdated: Date.now(), + }; + + // Cache the results + this.setCachedData(stats); + + return stats; + } catch (error) { + console.error('Error fetching GitHub organization stats:', error); + + // Return fallback data if API fails + const fallbackStats: GitHubOrgStats = { + totalStars: 0, + totalForks: 0, + totalRepositories: 0, + publicRepositories: 0, + totalContributors: 140, + discussionsCount: 0, + lastUpdated: Date.now(), + }; + + return fallbackStats; + } + } + + // Clear cache (useful for manual refresh) + clearCache(): void { + if (typeof window !== 'undefined') { + localStorage.removeItem(this.CACHE_KEY); + } + } + + // Get cache status + getCacheStatus(): { cached: boolean; age: number; expiresIn: number } { + const cached = this.getCachedData(); + if (!cached) { + return { cached: false, age: 0, expiresIn: 0 }; + } + + const age = Date.now() - cached.lastUpdated; + const expiresIn = Math.max(0, this.CACHE_DURATION - age); + + return { cached: true, age, expiresIn }; + } +} + +export const githubService = new GitHubService(); From 0910ea46526019a7a63990e06f7f654a25852b56 Mon Sep 17 00:00:00 2001 From: sanjay-kv Date: Thu, 26 Jun 2025 15:29:27 +1000 Subject: [PATCH 3/5] added file Signed-off-by: yashksaini-coder --- docs/GitHub/Maintainer-guide/milestone.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/GitHub/Maintainer-guide/milestone.md b/docs/GitHub/Maintainer-guide/milestone.md index b9ca81ee..7ca39bfa 100644 --- a/docs/GitHub/Maintainer-guide/milestone.md +++ b/docs/GitHub/Maintainer-guide/milestone.md @@ -49,7 +49,7 @@ Upon creation you will get a screen like this below, the explanation of each ele < /> -## Changing Readme and Adding more files. +## Changing Readme and Adding more files. Change Im going to add my resume and add my name as heading to the ``readme.md`` file From cd3611b5924ce9387d19e9b79f3114b31b2dd9e0 Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Thu, 26 Jun 2025 18:42:39 +0530 Subject: [PATCH 4/5] chore(app): Update the links to match required config Signed-off-by: yashksaini-coder --- src/components/Community/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Community/index.tsx b/src/components/Community/index.tsx index 40946ef6..57d14c08 100644 --- a/src/components/Community/index.tsx +++ b/src/components/Community/index.tsx @@ -27,7 +27,7 @@ export const LandingCommunity: FC = ({ className }) => { { stat: githubStarCountText, description: "Stars across all our GitHub repositories, showcasing the support and appreciation from the community.", - href: "https://github.com/recodehive", + href: "https://github.com/recodehive/Support", label: "GitHub Stars" }, { From 726a7ff2c0985d90930a3c9db5486e5f69c0bf8e Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Thu, 26 Jun 2025 20:06:07 +0530 Subject: [PATCH 5/5] feat(app): Add react-slot-counter animation to LandingCommunity Stats Signed-off-by: yashksaini-coder --- package.json | 1 + src/components/Community/LandingCommunity.css | 15 +++++ src/components/Community/index.tsx | 62 ++++++++----------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 41a2e596..5c27c976 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-icons": "^5.5.0", + "react-slot-counter": "^3.3.1", "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", "styled-components": "^6.1.18", diff --git a/src/components/Community/LandingCommunity.css b/src/components/Community/LandingCommunity.css index ff00248d..516f6954 100644 --- a/src/components/Community/LandingCommunity.css +++ b/src/components/Community/LandingCommunity.css @@ -89,6 +89,21 @@ position: relative; } +/* SlotCounter styling */ +.landing-community .slot-counter-number { + font-size: inherit; + font-weight: inherit; + color: inherit; + text-shadow: inherit; +} + +.landing-community .slot-counter-separator { + font-size: inherit; + font-weight: inherit; + color: inherit; + text-shadow: inherit; +} + .landing-community .landing-community__content .landing-community__stats .landing-community__stat-item .landing-community__loading { display: flex; align-items: center; diff --git a/src/components/Community/index.tsx b/src/components/Community/index.tsx index 57d14c08..b989b5b0 100644 --- a/src/components/Community/index.tsx +++ b/src/components/Community/index.tsx @@ -1,4 +1,5 @@ import React, { type FC, useEffect, useState, useMemo } from "react"; +import SlotCounter from 'react-slot-counter'; import "./LandingCommunity.css"; import { useCommunityStatsContext } from "@site/src/lib/statsProvider"; @@ -12,56 +13,45 @@ export const LandingCommunity: FC = ({ className }) => { githubContributorsCountText, githubForksCountText, githubReposCountText, + githubStarCount, + githubContributorsCount, + githubForksCount, + githubReposCount, loading, error } = useCommunityStatsContext(); - const [state, setState] = useState({ - stat0: 0, - stat1: 0, - stat2: 0, - stat3: 0, - }); - + const generateList = useMemo(() => [ { - stat: githubStarCountText, + stat: githubStarCount, + statText: githubStarCountText, description: "Stars across all our GitHub repositories, showcasing the support and appreciation from the community.", href: "https://github.com/recodehive/Support", label: "GitHub Stars" }, { - stat: githubReposCountText, + stat: githubReposCount, + statText: githubReposCountText, description: "Live public projects on RecodHive, demonstrating the power of open-source collaboration.", href: "https://github.com/orgs/recodehive/repositories?q=visibility%3Apublic+archived%3Afalse", label: "Public Repositories" }, { - stat: githubContributorsCountText, + stat: githubContributorsCount, + statText: githubContributorsCountText, description: "Amazing contributors who have made our repositories better and helped build our community.", href: "https://github.com/orgs/recodehive/people", label: "Contributors" }, { - stat: githubForksCountText, + stat: githubForksCount, + statText: githubForksCountText, description: "Forks of our repositories, showing how our community extends and builds upon our work.", href: "https://github.com/orgs/recodehive/discussions", label: "Community Forks" }, - ], [githubStarCountText, githubReposCountText, githubContributorsCountText, githubForksCountText]); - - const handleDynamicChange = (target: number, index: number) => { - let count = 0; - const increment = target / 100; - const interval = setInterval(() => { - count += increment; - setState(prev => ({ ...prev, [`stat${index}`]: Math.round(count) })); - if (count >= target) { - setState(prev => ({ ...prev, [`stat${index}`]: target })); - clearInterval(interval); - } - }, 20); - }; + ], [githubStarCount, githubStarCountText, githubReposCount, githubReposCountText, githubContributorsCount, githubContributorsCountText, githubForksCount, githubForksCountText]); const handleCardClick = (href: string) => { if (href) { @@ -69,17 +59,6 @@ export const LandingCommunity: FC = ({ className }) => { } }; - useEffect(() => { - if (!loading) { - generateList.forEach((item, index) => { - const numericStat = typeof item.stat === 'string' ? - parseInt(item.stat.replace(/[^\d]/g, '')) || 0 : - Number(item.stat); - handleDynamicChange(numericStat, index); - }); - } - }, [generateList, loading]); - return (
@@ -118,7 +97,16 @@ export const LandingCommunity: FC = ({ className }) => {
) : ( - {item.stat} + {item.href && } )}