diff --git a/src/components/charts/CombinedContributionChart.js b/src/components/charts/CombinedContributionChart.js index 682921b..9fdc0ce 100644 --- a/src/components/charts/CombinedContributionChart.js +++ b/src/components/charts/CombinedContributionChart.js @@ -8,6 +8,7 @@ const CombinedContributionChart = ({ session }) => { const [contributionData, setContributionData] = useState({}); const [totalContributions, setTotalContributions] = useState(0); const windowWidth = useWindowWidth(); + const [retryCount, setRetryCount] = useState(0); const prepareProgressData = useCallback(() => { if (!session?.user?.userCourses) return {}; @@ -85,7 +86,45 @@ const CombinedContributionChart = ({ session }) => { setTotalContributions(totalCommits + Object.values(activityData).reduce((a, b) => a + b, 0)); }, [prepareProgressData]); - const { data, isLoading, isFetching } = useFetchGithubCommits(session, handleNewCommit); + const { + data, + isLoading, + isFetching, + error, + refetch + } = useFetchGithubCommits(session, handleNewCommit); + + // Add recovery logic + useEffect(() => { + if (error && retryCount < 3) { + const timer = setTimeout(() => { + setRetryCount(prev => prev + 1); + refetch(); + }, 1000 * (retryCount + 1)); // Exponential backoff + + return () => clearTimeout(timer); + } + }, [error, retryCount, refetch]); + + // Reset retry count on successful data fetch + useEffect(() => { + if (data) { + setRetryCount(0); + } + }, [data]); + + // Add loading state check + useEffect(() => { + if (isLoading || isFetching) { + const loadingTimeout = setTimeout(() => { + if (!data) { + refetch(); + } + }, 5000); // Timeout after 5 seconds + + return () => clearTimeout(loadingTimeout); + } + }, [isLoading, isFetching, data, refetch]); // Initialize from cached data if available useEffect(() => { @@ -256,6 +295,16 @@ const CombinedContributionChart = ({ session }) => { More + {error && retryCount >= 3 && ( +
+ Error loading data. +
+ )} diff --git a/src/components/charts/GithubContributionChart.js b/src/components/charts/GithubContributionChart.js deleted file mode 100644 index 24c6df0..0000000 --- a/src/components/charts/GithubContributionChart.js +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useState, useCallback, useEffect } from 'react'; -import { useFetchGithubCommits } from '@/hooks/githubQueries/useFetchGithubCommits'; -import { Tooltip } from 'primereact/tooltip'; - -const GithubContributionChart = ({ username }) => { - const [contributionData, setContributionData] = useState({}); - const [totalCommits, setTotalCommits] = useState(0); - - const handleNewCommit = useCallback(({ contributionData, totalCommits }) => { - setContributionData(contributionData); - setTotalCommits(totalCommits); - }, []); - - const { data, isLoading, isFetching } = useFetchGithubCommits(username, handleNewCommit); - - // Initialize from cached data if available - useEffect(() => { - if (data && !isLoading) { - setContributionData(data.contributionData); - setTotalCommits(data.totalCommits); - } - }, [data, isLoading]); - - const getColor = useCallback((count) => { - if (count === 0) return 'bg-gray-100'; - if (count < 5) return 'bg-green-300'; - if (count < 10) return 'bg-green-400'; - if (count < 20) return 'bg-green-600'; - return 'bg-green-700'; - }, []); - - const generateCalendar = useCallback(() => { - const today = new Date(); - const oneYearAgo = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate()); - const calendar = []; - - // Create 7 rows for days of the week - for (let i = 0; i < 7; i++) { - calendar[i] = []; - } - - // Fill in the dates - for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) { - const dateString = d.toISOString().split('T')[0]; - const count = contributionData[dateString] || 0; - const dayOfWeek = d.getDay(); - calendar[dayOfWeek].push({ date: new Date(d), count }); - } - - return calendar; - }, [contributionData]); - - const calendar = generateCalendar(); - const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - - const getMonthLabels = useCallback(() => { - const today = new Date(); - const oneYearAgo = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate()); - const months = []; - let currentMonth = -1; - - for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) { - const month = d.getMonth(); - if (month !== currentMonth) { - months.push({ - name: d.toLocaleString('default', { month: 'short' }), - index: calendar[0].findIndex( - (_, weekIndex) => calendar[0][weekIndex]?.date.getMonth() === month - ) - }); - currentMonth = month; - } - } - return months; - }, [calendar]); - - return ( -
- {(isLoading || isFetching) &&

Loading contribution data... ({totalCommits} commits fetched)

} - {!isLoading && !isFetching && -
-

- {totalCommits} contributions in the last year -

- - -
- } -
- {/* Days of week labels */} -
- {weekDays.map((day, index) => ( -
- {index % 2 === 0 && day} -
- ))} -
-
- {/* Calendar grid */} -
- {calendar[0].map((_, weekIndex) => ( -
- {calendar.map((row, dayIndex) => ( - row[weekIndex] && ( -
- ) - ))} -
- ))} -
- {/* Month labels moved to bottom */} -
- {getMonthLabels().map((month, index) => ( -
- {month.name} -
- ))} -
-
-
-
- Less -
-
-
-
-
-
-
- More -
-
- ); -}; - -export default GithubContributionChart; diff --git a/src/components/charts/GithubContributionChartDisabled.js b/src/components/charts/GithubContributionChartDisabled.js deleted file mode 100644 index 1010a12..0000000 --- a/src/components/charts/GithubContributionChartDisabled.js +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useMemo } from 'react'; - -const GithubContributionChartDisabled = () => { - const getRandomColor = () => { - const random = Math.random(); - if (random < 0.4) return 'bg-gray-100'; - if (random < 0.6) return 'bg-green-300'; - if (random < 0.75) return 'bg-green-400'; - if (random < 0.9) return 'bg-green-600'; - return 'bg-green-700'; - }; - - const calendar = useMemo(() => { - const today = new Date(); - const sixMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 5, 1); - const calendar = []; - - for (let d = new Date(sixMonthsAgo); d <= today; d.setDate(d.getDate() + 1)) { - calendar.push({ date: new Date(d), color: getRandomColor() }); - } - - return calendar; - }, []); - - return ( -
-
-
- {calendar.map((day, index) => ( -
- ))} -
-
- Less -
-
-
-
-
-
-
- More -
-
-
-

Connect to GitHub (Coming Soon)

-
-
- ); -}; - -export default GithubContributionChartDisabled; diff --git a/src/components/profile/DataTables/UserProgressTable.js b/src/components/profile/DataTables/UserProgressTable.js index 57fc324..f9f0622 100644 --- a/src/components/profile/DataTables/UserProgressTable.js +++ b/src/components/profile/DataTables/UserProgressTable.js @@ -5,14 +5,28 @@ import useWindowWidth from "@/hooks/useWindowWidth"; import ProgressListItem from "@/components/content/lists/ProgressListItem"; import { formatDateTime } from "@/utils/time"; import { ProgressSpinner } from "primereact/progressspinner"; +import Link from 'next/link'; const UserProgressTable = ({ session, ndk }) => { - const windowWidth = useWindowWidth(); const prepareProgressData = () => { if (!session?.user?.userCourses) return []; const progressData = []; + // Add badge awards + session.user.userBadges?.forEach(userBadge => { + progressData.push({ + id: `badge-${userBadge.id}`, + type: 'badge', + name: userBadge.badge?.name, + eventType: 'awarded', + date: userBadge.awardedAt, + courseId: userBadge.badge?.courseId, + badgeId: userBadge.badgeId, + noteId: userBadge.badge?.noteId + }); + }); + session.user.userCourses.forEach(courseProgress => { // Add course start entry if (courseProgress.started) { @@ -86,25 +100,45 @@ const UserProgressTable = ({ session, ndk }) => { const typeTemplate = (rowData) => (
- + {rowData.type}
); const eventTemplate = (rowData) => (
- + {rowData.eventType}
); const nameTemplate = (rowData) => (
- {rowData.type === 'course' - ? - : - } + {rowData.type === 'badge' ? ( + + {rowData.name} + + ) : rowData.type === 'course' ? ( + + ) : ( + + )}
); diff --git a/src/hooks/apiQueries/useCompletedCoursesQuery.js b/src/hooks/apiQueries/useCompletedCoursesQuery.js index 7549f52..31d3e5d 100644 --- a/src/hooks/apiQueries/useCompletedCoursesQuery.js +++ b/src/hooks/apiQueries/useCompletedCoursesQuery.js @@ -1,9 +1,11 @@ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; import { useSession } from 'next-auth/react'; +import { useState } from 'react'; export function useCompletedCoursesQuery() { const { data: session } = useSession(); + const [retryCount, setRetryCount] = useState(0); const fetchCompletedCourses = async () => { if (!session?.user?.id) return []; @@ -17,7 +19,7 @@ export function useCompletedCoursesQuery() { return response.data; } catch (error) { console.error('Error fetching completed courses:', error); - return []; + throw error; // Let React Query handle the retry } }; @@ -29,5 +31,7 @@ export function useCompletedCoursesQuery() { cacheTime: 1000 * 60 * 30, // 30 minutes refetchOnWindowFocus: false, refetchOnMount: false, + retry: 3, + retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000), }); } \ No newline at end of file diff --git a/src/hooks/badges/useBadge.js b/src/hooks/badges/useBadge.js index 93b691d..61ad1d7 100644 --- a/src/hooks/badges/useBadge.js +++ b/src/hooks/badges/useBadge.js @@ -48,10 +48,21 @@ export const useBadge = () => { } if (badgesAwarded) { - // Update session silently - await update({ revalidate: false }); - // Invalidate the completed courses query to trigger a clean refetch + // First invalidate the queries await queryClient.invalidateQueries(['completedCourses']); + await queryClient.invalidateQueries(['githubCommits']); + + // Wait a brief moment before updating session + await new Promise(resolve => setTimeout(resolve, 100)); + + // Update session last + await update({ revalidate: false }); + + // Force a refetch of the invalidated queries + await Promise.all([ + queryClient.refetchQueries(['completedCourses']), + queryClient.refetchQueries(['githubCommits']) + ]); } } catch (error) { console.error('Error checking badge eligibility:', error); @@ -62,7 +73,8 @@ export const useBadge = () => { }; const timeoutId = setTimeout(checkForBadgeEligibility, 0); - const interval = setInterval(checkForBadgeEligibility, 300000); + // Reduce the frequency of checks to avoid potential race conditions + const interval = setInterval(checkForBadgeEligibility, 600000); // 10 minutes return () => { clearTimeout(timeoutId);