From d9cee62c8f3bbefe4c320ba007a03d1617dbcb81 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 09:22:02 -0600 Subject: [PATCH 01/20] Progress updates revamp Calculate real % of updates from start date of fundraise onward (no ending yet) Tooltip to tell users about 100 RSC incentive Multiple updates per month --- components/Fund/lib/FundUtils.ts | 77 +++++++++++++-- components/ui/ProgressUpdates.tsx | 95 ++++++++----------- components/ui/badges/UpdateRateBadge.tsx | 12 ++- components/work/FundDocument.tsx | 9 +- components/work/FundingRightSidebar.tsx | 3 +- components/work/components/UpdatesSection.tsx | 8 +- 6 files changed, 125 insertions(+), 79 deletions(-) diff --git a/components/Fund/lib/FundUtils.ts b/components/Fund/lib/FundUtils.ts index 978ca6c87..767ab3e31 100644 --- a/components/Fund/lib/FundUtils.ts +++ b/components/Fund/lib/FundUtils.ts @@ -4,35 +4,92 @@ interface Update { content?: any; } +interface FundraisingMetadata { + startDate?: string; + endDate?: string; +} + +interface WorkData { + createdDate: string; +} + +/** + * Determine the start date for when updates can begin posting + * @param fundraising - Fundraising metadata object + * @param work - Work object with creation date + * @returns ISO date string for when updates can start + */ +export const getUpdatesStartDate = ( + fundraising?: FundraisingMetadata | null, + work?: WorkData +): string => { + // Always use fundraise start date if available + if (fundraising?.startDate) { + return fundraising.startDate; + } + // Fallback to endDate minus 1 month if we have endDate + if (fundraising?.endDate) { + const endDate = new Date(fundraising.endDate); + endDate.setMonth(endDate.getMonth() - 1); + return endDate.toISOString(); + } + // Final fallback to work creation date + return work?.createdDate || new Date().toISOString(); +}; + /** - * Calculate the update rate as a percentage of months with updates in the last 12 months + * 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[]): number => { +export const calculateUpdateRate = (updates: Update[], startDate?: string): number => { if (updates.length === 0) { return 0; } const now = new Date(); - const updatesInLast12Months = updates.filter((update) => { + let start: Date; + + if (startDate) { + start = new Date(startDate); + } else { + // Fallback - should rarely be used since components should provide startDate + // Use earliest update date or recent date as last resort + if (updates.length > 0) { + 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); + } + } + + // Filter updates that are after the start date + const relevantUpdates = updates.filter((update) => { const updateDate = new Date(update.createdDate); - const monthsDiff = - (now.getFullYear() - updateDate.getFullYear()) * 12 + - (now.getMonth() - updateDate.getMonth()); - return monthsDiff <= 12; + 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(); - updatesInLast12Months.forEach((update) => { + 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 12 months - return Math.round((uniqueMonths.size / 12) * 100); + // 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 f3a5b8f7d..83a7ef35a 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -8,22 +8,41 @@ interface Update { interface ProgressUpdatesProps { updates: Update[]; + startDate?: string; // When updates can start being posted (e.g., fundraise start date) className?: string; } export const ProgressUpdates: React.FC = ({ updates = [], + startDate, className = '', }) => { - // Generate exactly 12 months starting from current date + // Generate timeline from startDate to current date const generateTimeline = () => { const timeline = []; const now = new Date(); - const startDate = new Date(now.getFullYear(), now.getMonth(), 1); // Start from current month - const current = new Date(startDate); - let monthCount = 0; - while (monthCount < 12) { + // If no startDate provided, use the earliest update date or current date minus 3 months + let start: Date; + if (startDate) { + start = new Date(startDate); + } 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 current = new Date(start); + const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); + + // Generate months from start to current month + while (current <= currentMonthStart) { const monthYear = `${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, '0')}`; const monthName = current.toLocaleDateString('en-US', { month: 'short' }); const year = current.getFullYear(); @@ -45,7 +64,6 @@ export const ProgressUpdates: React.FC = ({ }); current.setMonth(current.getMonth() + 1); - monthCount++; } return timeline; @@ -53,26 +71,30 @@ export const ProgressUpdates: React.FC = ({ const timeline = generateTimeline(); + // Don't render if timeline is empty + if (timeline.length === 0) { + return null; + } + return (
{/* Monthly Timeline */}
{timeline.map((month) => { - const now = new Date(); - const monthDate = new Date(month.year, new Date(`${month.monthName} 1, 2000`).getMonth()); - const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); - const isInPast = monthDate < currentMonthStart; - const isPastWithoutUpdates = isInPast && !month.hasUpdate; + const displayText = + month.updateCount > 1 + ? `${month.monthName} ${String(month.year).slice(-2)} x${month.updateCount}` + : `${month.monthName} ${String(month.year).slice(-2)}`; return (
= ({ : `${month.monthName} ${month.year} - No updates` } > - {/* Diagonal lines for past months without updates */} - {isPastWithoutUpdates && ( -
- )} - -
{month.monthName}
-
- {month.year.toString().slice(-2)} -
- - {/* Update Count Badge - Top Right Corner */} - {month.hasUpdate && ( -
- {month.updateCount} -
- )} +
{displayText}
); })} @@ -122,22 +116,7 @@ export const ProgressUpdates: React.FC = ({ Has updates
-
+
No updates
diff --git a/components/ui/badges/UpdateRateBadge.tsx b/components/ui/badges/UpdateRateBadge.tsx index 7d7a4006e..a4a066629 100644 --- a/components/ui/badges/UpdateRateBadge.tsx +++ b/components/ui/badges/UpdateRateBadge.tsx @@ -11,19 +11,25 @@ export const UpdateRateBadge = ({ updateRate, className = '' }: UpdateRateBadgeP const tooltipContent = (

Update Rate

-

+

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 */}
{timeline.map((month) => { - const displayText = - month.updateCount > 1 - ? `${month.monthName} ${String(month.year).slice(-2)} x${month.updateCount}` - : `${month.monthName} ${String(month.year).slice(-2)}`; + const displayText = `${month.monthName} ${String(month.year).slice(-2)}`; return (
= ({ } >
{displayText}
+ + {/* Notification Badge for multiple updates */} + {month.updateCount > 1 && ( +
+ {month.updateCount} +
+ )}
); })} From de9ec706b75452b933b7bd09cd1b1f3d69304b11 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:03:22 -0600 Subject: [PATCH 04/20] Soften color of # updates & fix width of month box --- components/ui/ProgressUpdates.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 3ab748435..8fd78ff7b 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -89,7 +89,7 @@ export const ProgressUpdates: React.FC = ({
= ({ {/* Notification Badge for multiple updates */} {month.updateCount > 1 && ( -
+
{month.updateCount}
)} From f4f1198c8ae5f4aa2395525c91bfb26ca9325636 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:09:48 -0600 Subject: [PATCH 05/20] use grays to color # updates --- components/ui/ProgressUpdates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 8fd78ff7b..32a8f637a 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -106,7 +106,7 @@ export const ProgressUpdates: React.FC = ({ {/* Notification Badge for multiple updates */} {month.updateCount > 1 && ( -
+
{month.updateCount}
)} From 287eaccd29f6e7b3b0a642f815fbce332e486c92 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:14:39 -0600 Subject: [PATCH 06/20] try green --- components/ui/ProgressUpdates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 32a8f637a..8e94f5d0f 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -106,7 +106,7 @@ export const ProgressUpdates: React.FC = ({ {/* Notification Badge for multiple updates */} {month.updateCount > 1 && ( -
+
{month.updateCount}
)} From 3af85a2c188747e8092cfb7dc9ad36749e4cd056 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:25:07 -0600 Subject: [PATCH 07/20] try darker --- components/ui/ProgressUpdates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 8e94f5d0f..5487eb1a7 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -106,7 +106,7 @@ export const ProgressUpdates: React.FC = ({ {/* Notification Badge for multiple updates */} {month.updateCount > 1 && ( -
+
{month.updateCount}
)} From 3f31159d8842814a0100becd5a5d55be69805736 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:32:08 -0600 Subject: [PATCH 08/20] nav to updates when clicking a month --- components/ui/ProgressUpdates.tsx | 7 +++++-- components/work/FundDocument.tsx | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 5487eb1a7..62073da0d 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -10,12 +10,14 @@ interface ProgressUpdatesProps { updates: Update[]; startDate?: string; // When updates can start being posted (e.g., fundraise start date) className?: string; + onMonthClick?: () => void; // Optional callback for when a month with updates is clicked } export const ProgressUpdates: React.FC = ({ updates = [], startDate, className = '', + onMonthClick, }) => { // Generate timeline from startDate to current date const generateTimeline = () => { @@ -92,15 +94,16 @@ export const ProgressUpdates: React.FC = ({ relative px-2 py-1.5 rounded-md border text-center transition-all flex-shrink-0 w-16 ${ month.hasUpdate - ? 'bg-green-50 border-green-200 text-green-700 hover:bg-green-100' + ? `bg-green-50 border-green-200 text-green-700 hover:bg-green-100 ${onMonthClick ? 'cursor-pointer' : ''}` : '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' : ''}${onMonthClick ? ' (Click to view)' : ''}` : `${month.monthName} ${month.year} - No updates` } + onClick={month.hasUpdate && onMonthClick ? onMonthClick : undefined} >
{displayText}
diff --git a/components/work/FundDocument.tsx b/components/work/FundDocument.tsx index 26d6fada9..2f9598855 100644 --- a/components/work/FundDocument.tsx +++ b/components/work/FundDocument.tsx @@ -138,6 +138,7 @@ export const FundDocument = ({ content: update.content, }))} startDate={getUpdatesStartDate(metadata.fundraising, work)} + onMonthClick={() => handleTabChange('updates')} />
From f0dffb6c8def924c0a70e4dbdd6e31c72d50c59d Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:38:04 -0600 Subject: [PATCH 09/20] Revert "nav to updates when clicking a month" This reverts commit 3f31159d8842814a0100becd5a5d55be69805736. --- components/ui/ProgressUpdates.tsx | 7 ++----- components/work/FundDocument.tsx | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 62073da0d..5487eb1a7 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -10,14 +10,12 @@ interface ProgressUpdatesProps { updates: Update[]; startDate?: string; // When updates can start being posted (e.g., fundraise start date) className?: string; - onMonthClick?: () => void; // Optional callback for when a month with updates is clicked } export const ProgressUpdates: React.FC = ({ updates = [], startDate, className = '', - onMonthClick, }) => { // Generate timeline from startDate to current date const generateTimeline = () => { @@ -94,16 +92,15 @@ export const ProgressUpdates: React.FC = ({ relative px-2 py-1.5 rounded-md border text-center transition-all flex-shrink-0 w-16 ${ month.hasUpdate - ? `bg-green-50 border-green-200 text-green-700 hover:bg-green-100 ${onMonthClick ? 'cursor-pointer' : ''}` + ? 'bg-green-50 border-green-200 text-green-700 hover:bg-green-100' : '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' : ''}${onMonthClick ? ' (Click to view)' : ''}` + ? `${month.monthName} ${month.year} - ${month.updateCount} update${month.updateCount > 1 ? 's' : ''}` : `${month.monthName} ${month.year} - No updates` } - onClick={month.hasUpdate && onMonthClick ? onMonthClick : undefined} >
{displayText}
diff --git a/components/work/FundDocument.tsx b/components/work/FundDocument.tsx index 2f9598855..26d6fada9 100644 --- a/components/work/FundDocument.tsx +++ b/components/work/FundDocument.tsx @@ -138,7 +138,6 @@ export const FundDocument = ({ content: update.content, }))} startDate={getUpdatesStartDate(metadata.fundraising, work)} - onMonthClick={() => handleTabChange('updates')} />
From 2e156e9c5d38d9bf67f47546706a044cf29ce6ec Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:49:45 -0600 Subject: [PATCH 10/20] Fix sonarcloud issues --- components/Fund/lib/FundUtils.ts | 18 ++++++++---------- components/work/FundDocument.tsx | 3 +-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/components/Fund/lib/FundUtils.ts b/components/Fund/lib/FundUtils.ts index 767ab3e31..a37bc9696 100644 --- a/components/Fund/lib/FundUtils.ts +++ b/components/Fund/lib/FundUtils.ts @@ -54,18 +54,16 @@ export const calculateUpdateRate = (updates: Update[], startDate?: string): numb if (startDate) { start = new Date(startDate); - } else { + } 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 - if (updates.length > 0) { - 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 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); } // Filter updates that are after the start date diff --git a/components/work/FundDocument.tsx b/components/work/FundDocument.tsx index 26d6fada9..c39df82e7 100644 --- a/components/work/FundDocument.tsx +++ b/components/work/FundDocument.tsx @@ -14,8 +14,7 @@ import { PostBlockEditor } from './PostBlockEditor'; 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 { calculateUpdateRate, getUpdatesStartDate } from '@/components/Fund/lib/FundUtils'; import { FundingRightSidebar } from './FundingRightSidebar'; import { useUser } from '@/contexts/UserContext'; import { UpdateRateBadge } from '@/components/ui/badges/UpdateRateBadge'; From 8eda8f7d52907ec26a27bdc157a623c2b77cb0c9 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:46:20 -0600 Subject: [PATCH 11/20] Lighten color on # updates --- components/ui/ProgressUpdates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 5487eb1a7..12ea85d6c 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -106,7 +106,7 @@ export const ProgressUpdates: React.FC = ({ {/* Notification Badge for multiple updates */} {month.updateCount > 1 && ( -
+
{month.updateCount}
)} From 9a731f90f78c665ff433368b1519396d336bcd33 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:22:22 -0700 Subject: [PATCH 12/20] try alt design on # updates --- components/ui/ProgressUpdates.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 12ea85d6c..69a0c10d9 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -83,13 +83,16 @@ export const ProgressUpdates: React.FC = ({ {/* Monthly Timeline */}
{timeline.map((month) => { - const displayText = `${month.monthName} ${String(month.year).slice(-2)}`; + const displayText = + month.updateCount > 1 + ? `${month.monthName} ${String(month.year).slice(-2)} (${month.updateCount})` + : `${month.monthName} ${String(month.year).slice(-2)}`; return (
= ({ } >
{displayText}
- - {/* Notification Badge for multiple updates */} - {month.updateCount > 1 && ( -
- {month.updateCount} -
- )}
); })} From 56d96fbc0ba108d3657ea768e33550e9fe66c6e1 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:30:55 -0700 Subject: [PATCH 13/20] use x3 and bigger month text --- components/ui/ProgressUpdates.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 69a0c10d9..955b3aa61 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -83,10 +83,7 @@ export const ProgressUpdates: React.FC = ({ {/* Monthly Timeline */}
{timeline.map((month) => { - const displayText = - month.updateCount > 1 - ? `${month.monthName} ${String(month.year).slice(-2)} (${month.updateCount})` - : `${month.monthName} ${String(month.year).slice(-2)}`; + const monthText = `${month.monthName} ${String(month.year).slice(-2)}`; return (
= ({ : `${month.monthName} ${month.year} - No updates` } > -
{displayText}
+
+ {monthText} + {month.updateCount > 1 && ( + x{month.updateCount} + )} +
); })} From 01827496f1144da5e2136fdb5f711e785917eedd Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:51:45 -0700 Subject: [PATCH 14/20] lighten x3 --- components/ui/ProgressUpdates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 955b3aa61..c31154c94 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -105,7 +105,7 @@ export const ProgressUpdates: React.FC = ({
{monthText} {month.updateCount > 1 && ( - x{month.updateCount} + x{month.updateCount} )}
From f1199d8ff45b7dc7766fc3ca37c939ed69752761 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:59:14 -0700 Subject: [PATCH 15/20] green 600 --- components/ui/ProgressUpdates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index c31154c94..f8fe933b2 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -105,7 +105,7 @@ export const ProgressUpdates: React.FC = ({
{monthText} {month.updateCount > 1 && ( - x{month.updateCount} + x{month.updateCount} )}
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` } >
- {monthText} + + {monthText} + {month.updateCount > 1 && ( - x{month.updateCount} + + x{month.updateCount} + )}
@@ -100,7 +108,7 @@ export const ProgressUpdates: React.FC = ({ {/* Compact Legend */}
-
+
Has updates
diff --git a/components/ui/badges/UpdateRateBadge.tsx b/components/ui/badges/UpdateRateBadge.tsx deleted file mode 100644 index a4a066629..000000000 --- a/components/ui/badges/UpdateRateBadge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; - -import { Tooltip } from '@/components/ui/Tooltip'; - -interface UpdateRateBadgeProps { - updateRate: number; - className?: string; -} - -export const UpdateRateBadge = ({ updateRate, className = '' }: UpdateRateBadgeProps) => { - const tooltipContent = ( -
-

Update Rate

-

- 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.

-
- -
({ diff --git a/components/work/components/UpdatesSection.tsx b/components/work/components/UpdatesSection.tsx index 84d8033ce..21a3ab9e9 100644 --- a/components/work/components/UpdatesSection.tsx +++ b/components/work/components/UpdatesSection.tsx @@ -2,8 +2,6 @@ import { Bell } from 'lucide-react'; import { ProgressUpdates } from '@/components/ui/ProgressUpdates'; -import { calculateUpdateRate } from '@/components/Fund/lib/FundUtils'; -import { UpdateRateBadge } from '@/components/ui/badges/UpdateRateBadge'; interface Update { id: number; @@ -18,8 +16,6 @@ interface UpdatesSectionProps { } export const UpdatesSection = ({ updates = [], startDate, className }: UpdatesSectionProps) => { - const updateRate = calculateUpdateRate(updates, startDate); - return (
@@ -27,9 +23,6 @@ export const UpdatesSection = ({ updates = [], startDate, className }: UpdatesSe

Author Updates

-
- -
From 4ba5df027b956ae61b5aed742e961e74d38f1ea0 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Mon, 11 Aug 2025 07:30:21 -0700 Subject: [PATCH 18/20] Update ProgressUpdates.tsx --- components/ui/ProgressUpdates.tsx | 36 ++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 996000537..86a87163d 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -1,4 +1,7 @@ +'use client'; + import React from 'react'; +import { useRouter, usePathname } from 'next/navigation'; import { getTimelineStartDate } from '@/components/Fund/lib/FundUtils'; interface Update { @@ -18,6 +21,18 @@ export const ProgressUpdates: React.FC = ({ startDate, className = '', }) => { + const router = useRouter(); + const pathname = usePathname(); + + const navigateToUpdatesTab = () => { + if (!pathname) return; + const basePath = pathname.replace( + /\/(updates|conversation|applications|reviews|bounties|history)$/i, + '' + ); + const target = `${basePath}/updates`; + router.push(target); + }; // Generate timeline from startDate to current date const generateTimeline = () => { const timeline = []; @@ -89,15 +104,20 @@ export const ProgressUpdates: React.FC = ({ } >
- - {monthText} - - {month.updateCount > 1 && ( - - x{month.updateCount} - + {monthText} + + ) : ( + {monthText} + )} + {month.updateCount > 1 && ( + x{month.updateCount} )}
From aec31360b55a35b1f9c403e4356f65dbf6713f78 Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Mon, 11 Aug 2025 07:55:36 -0700 Subject: [PATCH 19/20] entire month/year button is clickable, not just underline --- components/ui/ProgressUpdates.tsx | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 86a87163d..42037f45a 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -85,14 +85,17 @@ export const ProgressUpdates: React.FC = ({ const monthText = `${month.monthName} ${String(month.year).slice(-2)}`; return ( -
= ({ }` : `${month.monthName} ${month.year} - No updates` } + aria-label={ + month.hasUpdate + ? `View updates for ${month.monthName} ${month.year}` + : `${month.monthName} ${month.year} - No updates` + } >
- {month.hasUpdate ? ( - - ) : ( - {monthText} - )} + + {monthText} + {month.updateCount > 1 && ( x{month.updateCount} )}
-
+ ); })}
From 9a9a7ec64e910beeb13c2bd28a4bbb9cb3682f3f Mon Sep 17 00:00:00 2001 From: Tyler Diorio <109099227+TylerDiorio@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:20:39 -0700 Subject: [PATCH 20/20] try vertical --- components/ui/ProgressUpdates.tsx | 68 ++++++++++--------------------- 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index 42037f45a..a737ca62d 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -80,61 +80,37 @@ export const ProgressUpdates: React.FC = ({ return (
{/* Monthly Timeline */} -
+
{timeline.map((month) => { const monthText = `${month.monthName} ${String(month.year).slice(-2)}`; return ( - + ) : ( + {monthText} - {month.updateCount > 1 && ( - x{month.updateCount} - )} -
- + )} + {month.updateCount > 1 && ( + x {month.updateCount} + )} +
); })}
- - {/* Compact Legend */} -
-
-
- Has updates -
-
-
- No updates -
-
); };