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}
-
- ))}
-
-
-
-
-
- );
-};
-
-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) => (
-
- ))}
-
-
-
-
-
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);