This statistic aims to inform how communicative the authors are during the course of their
research.
+
+
+ RSC Incentive: ResearchHub incentivizes researchers to
+ share real-time progress updates monthly. Authors receive 100 RSC for each monthly update to
+ maximize open science and make research more reproducible and impactful.
+
);
return (
-
+
- 100% update rate
+ {updateRate}% update rate
);
diff --git a/components/work/FundDocument.tsx b/components/work/FundDocument.tsx
index f55e00e11..26d6fada9 100644
--- a/components/work/FundDocument.tsx
+++ b/components/work/FundDocument.tsx
@@ -15,6 +15,7 @@ import { FundraiseProgress } from '@/components/Fund/FundraiseProgress';
import { ProgressUpdates } from '@/components/ui/ProgressUpdates';
import { useStorageKey } from '@/utils/storageKeys';
import { calculateUpdateRate } from '@/components/Fund/lib/FundUtils';
+import { getUpdatesStartDate } from '@/components/Fund/lib/FundUtils';
import { FundingRightSidebar } from './FundingRightSidebar';
import { useUser } from '@/contexts/UserContext';
import { UpdateRateBadge } from '@/components/ui/badges/UpdateRateBadge';
@@ -122,7 +123,12 @@ export const FundDocument = ({
-
+
diff --git a/components/work/FundingRightSidebar.tsx b/components/work/FundingRightSidebar.tsx
index 61de2e995..5768bb71c 100644
--- a/components/work/FundingRightSidebar.tsx
+++ b/components/work/FundingRightSidebar.tsx
@@ -9,6 +9,7 @@ import { NonprofitSection } from './components/NonprofitSection';
import { FundersSection } from './components/FundersSection';
import { ApplicantsSection } from './components/ApplicantsSection';
import { UpdatesSection } from './components/UpdatesSection';
+import { getUpdatesStartDate } from '@/components/Fund/lib/FundUtils';
interface FundingRightSidebarProps {
work: Work;
@@ -37,7 +38,7 @@ export const FundingRightSidebar = ({
createdDate: comment.createdDate,
content: comment.content,
}))}
- startDate={work.createdDate}
+ startDate={getUpdatesStartDate(metadata.fundraising, work)}
className="p-0"
/>
{/* Applicants for the grant */}
diff --git a/components/work/components/UpdatesSection.tsx b/components/work/components/UpdatesSection.tsx
index 0bbd365f6..84d8033ce 100644
--- a/components/work/components/UpdatesSection.tsx
+++ b/components/work/components/UpdatesSection.tsx
@@ -18,11 +18,7 @@ interface UpdatesSectionProps {
}
export const UpdatesSection = ({ updates = [], startDate, className }: UpdatesSectionProps) => {
- if (updates.length === 0 && !startDate) {
- return null;
- }
-
- const updateRate = calculateUpdateRate(updates);
+ const updateRate = calculateUpdateRate(updates, startDate);
return (
@@ -36,7 +32,7 @@ export const UpdatesSection = ({ updates = [], startDate, className }: UpdatesSe
-
+
);
};
From 231a7b52c2bce5461969a076974224da4e6b695d Mon Sep 17 00:00:00 2001
From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com>
Date: Wed, 23 Jul 2025 10:45:18 -0600
Subject: [PATCH 02/20] Include 1st month open as possible for updates
---
components/ui/ProgressUpdates.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx
index 83a7ef35a..c27154580 100644
--- a/components/ui/ProgressUpdates.tsx
+++ b/components/ui/ProgressUpdates.tsx
@@ -25,7 +25,9 @@ export const ProgressUpdates: React.FC = ({
// If no startDate provided, use the earliest update date or current date minus 3 months
let start: Date;
if (startDate) {
- start = new Date(startDate);
+ const startDateObj = new Date(startDate);
+ // Normalize to the beginning of the start month to ensure we include the full month
+ start = new Date(startDateObj.getFullYear(), startDateObj.getMonth(), 1);
} else if (updates.length > 0) {
// Find the earliest update
const earliestUpdate = updates.reduce((earliest, update) => {
@@ -41,7 +43,7 @@ export const ProgressUpdates: React.FC = ({
const current = new Date(start);
const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
- // Generate months from start to current month
+ // Generate months from start to current month (inclusive)
while (current <= currentMonthStart) {
const monthYear = `${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, '0')}`;
const monthName = current.toLocaleDateString('en-US', { month: 'short' });
From a15a694e97ffc82a108770cf7efa475ecaa106c3 Mon Sep 17 00:00:00 2001
From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com>
Date: Wed, 23 Jul 2025 10:54:34 -0600
Subject: [PATCH 03/20] Better UI for # updates per month
---
components/ui/ProgressUpdates.tsx | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx
index c27154580..3ab748435 100644
--- a/components/ui/ProgressUpdates.tsx
+++ b/components/ui/ProgressUpdates.tsx
@@ -83,10 +83,7 @@ export const ProgressUpdates: React.FC = ({
{/* Monthly Timeline */}
From 292a46654816d7ff1b31c55f23d0e9eef1eae23c Mon Sep 17 00:00:00 2001
From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com>
Date: Mon, 28 Jul 2025 18:50:18 -0600
Subject: [PATCH 16/20] refactor: Centralize timeline start date logic into
helper function
- Extracted the complex start date calculation logic from `ProgressUpdates.tsx` into a new, reusable helper function `getTimelineStartDate` in `FundUtils.ts`.
Improve maintainability and ensures that both the visual timeline (`ProgressUpdates`) and the rate calculation (`calculateUpdateRate`) use the exact same source of truth for their start date.
- **New:** Created `getTimelineStartDate` in `FundUtils.ts` to handle all fallback and normalization logic for determining the timeline's start.
- **Update:** Refactored `ProgressUpdates.tsx` to remove its local date logic and call the new centralized helper function.
- **Update:** Refactored `calculateUpdateRate` to also use `getTimelineStartDate`, ensuring perfect consistency between the UI and the data calculation.
---
components/Fund/lib/FundUtils.ts | 44 ++++++++++++++++++++-----------
components/ui/ProgressUpdates.tsx | 20 ++------------
2 files changed, 31 insertions(+), 33 deletions(-)
diff --git a/components/Fund/lib/FundUtils.ts b/components/Fund/lib/FundUtils.ts
index a37bc9696..3a5de879a 100644
--- a/components/Fund/lib/FundUtils.ts
+++ b/components/Fund/lib/FundUtils.ts
@@ -37,6 +37,34 @@ export const getUpdatesStartDate = (
return work?.createdDate || new Date().toISOString();
};
+/**
+ * Determines the normalized start date for a timeline.
+ * @param startDate - Optional preferred start date string.
+ * @param updates - Array of updates, used as a fallback.
+ * @returns A normalized Date object for the start of the timeline.
+ */
+export const getTimelineStartDate = (startDate?: string, updates: Update[] = []): Date => {
+ const now = new Date();
+
+ if (startDate) {
+ const startDateObj = new Date(startDate);
+ // Normalize to the beginning of the start month to ensure we include the full month
+ return new Date(startDateObj.getFullYear(), startDateObj.getMonth(), 1);
+ }
+
+ if (updates.length > 0) {
+ // Find the earliest update
+ const earliestUpdate = updates.reduce((earliest, update) => {
+ const updateDate = new Date(update.createdDate);
+ return updateDate < earliest ? updateDate : earliest;
+ }, new Date(updates[0].createdDate));
+ return new Date(earliestUpdate.getFullYear(), earliestUpdate.getMonth(), 1);
+ }
+
+ // Default to 3 months ago
+ return new Date(now.getFullYear(), now.getMonth() - 2, 1);
+};
+
/**
* Calculate the update rate as a percentage of months with updates since a start date
* Only the first update in each month counts towards the rate
@@ -50,21 +78,7 @@ export const calculateUpdateRate = (updates: Update[], startDate?: string): numb
}
const now = new Date();
- let start: Date;
-
- if (startDate) {
- start = new Date(startDate);
- } else if (updates.length > 0) {
- // Fallback - should rarely be used since components should provide startDate
- // Use earliest update date or recent date as last resort
- const earliestUpdate = updates.reduce((earliest, update) => {
- const updateDate = new Date(update.createdDate);
- return updateDate < earliest ? updateDate : earliest;
- }, new Date(updates[0].createdDate));
- start = new Date(earliestUpdate.getFullYear(), earliestUpdate.getMonth(), 1);
- } else {
- start = new Date(now.getFullYear(), now.getMonth() - 2, 1);
- }
+ const start = getTimelineStartDate(startDate, updates);
// Filter updates that are after the start date
const relevantUpdates = updates.filter((update) => {
diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx
index f8fe933b2..16fac2eaf 100644
--- a/components/ui/ProgressUpdates.tsx
+++ b/components/ui/ProgressUpdates.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import { getTimelineStartDate } from '@/components/Fund/lib/FundUtils';
interface Update {
id: number;
@@ -22,24 +23,7 @@ export const ProgressUpdates: React.FC = ({
const timeline = [];
const now = new Date();
- // If no startDate provided, use the earliest update date or current date minus 3 months
- let start: Date;
- if (startDate) {
- const startDateObj = new Date(startDate);
- // Normalize to the beginning of the start month to ensure we include the full month
- start = new Date(startDateObj.getFullYear(), startDateObj.getMonth(), 1);
- } else if (updates.length > 0) {
- // Find the earliest update
- const earliestUpdate = updates.reduce((earliest, update) => {
- const updateDate = new Date(update.createdDate);
- return updateDate < earliest ? updateDate : earliest;
- }, new Date(updates[0].createdDate));
- start = new Date(earliestUpdate.getFullYear(), earliestUpdate.getMonth(), 1);
- } else {
- // Default to 3 months ago
- start = new Date(now.getFullYear(), now.getMonth() - 2, 1);
- }
-
+ const start = getTimelineStartDate(startDate, updates);
const current = new Date(start);
const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
From b9a1c90239e685686c2d5f2f0360ddb53a5609b9 Mon Sep 17 00:00:00 2001
From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com>
Date: Mon, 11 Aug 2025 07:20:16 -0700
Subject: [PATCH 17/20] Remove update %, change green -> gray, underline =
active
---
components/Fund/lib/FundUtils.ts | 41 -------------------
components/ui/ProgressUpdates.tsx | 18 +++++---
components/ui/badges/UpdateRateBadge.tsx | 36 ----------------
components/work/FundDocument.tsx | 11 +----
components/work/components/UpdatesSection.tsx | 7 ----
5 files changed, 14 insertions(+), 99 deletions(-)
delete mode 100644 components/ui/badges/UpdateRateBadge.tsx
diff --git a/components/Fund/lib/FundUtils.ts b/components/Fund/lib/FundUtils.ts
index 3a5de879a..1bbe4f4b1 100644
--- a/components/Fund/lib/FundUtils.ts
+++ b/components/Fund/lib/FundUtils.ts
@@ -64,44 +64,3 @@ export const getTimelineStartDate = (startDate?: string, updates: Update[] = [])
// Default to 3 months ago
return new Date(now.getFullYear(), now.getMonth() - 2, 1);
};
-
-/**
- * Calculate the update rate as a percentage of months with updates since a start date
- * Only the first update in each month counts towards the rate
- * @param updates - Array of updates with createdDate
- * @param startDate - Start date to calculate from (should always be provided)
- * @returns Percentage (0-100) representing how many months had updates
- */
-export const calculateUpdateRate = (updates: Update[], startDate?: string): number => {
- if (updates.length === 0) {
- return 0;
- }
-
- const now = new Date();
- const start = getTimelineStartDate(startDate, updates);
-
- // Filter updates that are after the start date
- const relevantUpdates = updates.filter((update) => {
- const updateDate = new Date(update.createdDate);
- return updateDate >= start && updateDate <= now;
- });
-
- // Calculate the number of months in the period
- const monthsDiff =
- (now.getFullYear() - start.getFullYear()) * 12 + (now.getMonth() - start.getMonth()) + 1;
-
- // Use actual months for calculation (no cap)
- const monthsToConsider = Math.max(1, monthsDiff); // At least 1 month
-
- // Group updates by month-year and only count unique months
- const uniqueMonths = new Set();
-
- relevantUpdates.forEach((update) => {
- const updateDate = new Date(update.createdDate);
- const monthYear = `${updateDate.getFullYear()}-${updateDate.getMonth()}`;
- uniqueMonths.add(monthYear);
- });
-
- // Calculate percentage based on unique months with updates out of total months
- return Math.round((uniqueMonths.size / monthsToConsider) * 100);
-};
diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx
index 16fac2eaf..996000537 100644
--- a/components/ui/ProgressUpdates.tsx
+++ b/components/ui/ProgressUpdates.tsx
@@ -76,20 +76,28 @@ export const ProgressUpdates: React.FC = ({
relative px-2 py-1.5 rounded-md border text-center transition-all flex-shrink-0
${
month.hasUpdate
- ? 'bg-green-50 border-green-200 text-green-700 hover:bg-green-100'
+ ? 'bg-gray-100 border-gray-300 text-gray-600 hover:bg-gray-200'
: 'bg-white border-gray-200 text-gray-500 hover:bg-gray-50'
}
`}
title={
month.hasUpdate
- ? `${month.monthName} ${month.year} - ${month.updateCount} update${month.updateCount > 1 ? 's' : ''}`
+ ? `${month.monthName} ${month.year} - ${month.updateCount} update${
+ month.updateCount > 1 ? 's' : ''
+ }`
: `${month.monthName} ${month.year} - No updates`
}
>
- This statistic aims to inform how communicative the authors are during the course of their
- research.
-
-
-
- RSC Incentive: ResearchHub incentivizes researchers to
- share real-time progress updates monthly. Authors receive 100 RSC for each monthly update to
- maximize open science and make research more reproducible and impactful.
-
-
- );
-
- return (
-
-
- {updateRate}% update rate
-
-
- );
-};
diff --git a/components/work/FundDocument.tsx b/components/work/FundDocument.tsx
index c39df82e7..87d9fa1de 100644
--- a/components/work/FundDocument.tsx
+++ b/components/work/FundDocument.tsx
@@ -14,10 +14,9 @@ import { PostBlockEditor } from './PostBlockEditor';
import { FundraiseProgress } from '@/components/Fund/FundraiseProgress';
import { ProgressUpdates } from '@/components/ui/ProgressUpdates';
import { useStorageKey } from '@/utils/storageKeys';
-import { calculateUpdateRate, getUpdatesStartDate } from '@/components/Fund/lib/FundUtils';
+import { getUpdatesStartDate } from '@/components/Fund/lib/FundUtils';
import { FundingRightSidebar } from './FundingRightSidebar';
import { useUser } from '@/contexts/UserContext';
-import { UpdateRateBadge } from '@/components/ui/badges/UpdateRateBadge';
import { EarningOpportunityBanner } from '@/components/banners/EarningOpportunityBanner';
import { useShareModalContext } from '@/contexts/ShareContext';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -121,14 +120,6 @@ export const FundDocument = ({
Track updates made by authors about their research progress.