diff --git a/src/pages/ContributorProfilePage.jsx b/src/pages/ContributorProfilePage.jsx
index 3cafec8..9340dee 100644
--- a/src/pages/ContributorProfilePage.jsx
+++ b/src/pages/ContributorProfilePage.jsx
@@ -4,14 +4,18 @@ import { FiArrowLeft, FiDownload, FiExternalLink, FiCalendar, FiBriefcase, FiAle
import { useApp } from '../context/AppContext'
import { C, PageTitle, Spinner, StatCard } from '../components/UI'
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
+import {
+ isInvalidReportingRange,
+ INVALID_REPORTING_RANGE_MESSAGE,
+} from '../utils/reportingDateRange'
// Reusable ContributionTable component
-function ContributionTable({ items, dateHeader, resolveStatus }) {
+function ContributionTable({ items, dateHeader, resolveStatus, emptyMessage }) {
if (!items.length) {
return (
-
No items found for this reporting period.
+
{emptyMessage || 'No items found for this reporting period.'}
)
}
@@ -245,8 +249,11 @@ export default function ContributorProfilePage() {
}
}
- // Filter contributions by UTC date range limits
+ const invalidDateRange = isInvalidReportingRange(startDate, endDate)
+
+ // Filter contributions by UTC date range limits (skip when range is invalid)
const filteredContribs = useMemo(() => {
+ if (isInvalidReportingRange(startDate, endDate)) return []
return rawContributions.filter(item => {
const itemTime = new Date(item.created_at).getTime()
if (startDate) {
@@ -424,7 +431,7 @@ export default function ContributorProfilePage() {
right={
Export Contribution Report (.md)
@@ -469,8 +476,10 @@ export default function ContributorProfilePage() {
id="start-date-input"
type="date"
value={startDate}
+ max={endDate || undefined}
onChange={e => setStartDate(e.target.value)}
style={C.input}
+ aria-invalid={invalidDateRange}
/>
to
@@ -480,22 +489,62 @@ export default function ContributorProfilePage() {
id="end-date-input"
type="date"
value={endDate}
+ min={startDate || undefined}
onChange={e => setEndDate(e.target.value)}
style={C.input}
+ aria-invalid={invalidDateRange}
/>
+ {invalidDateRange && (
+
+
+
+ {INVALID_REPORTING_RANGE_MESSAGE}
+
+
+ )}
{/* Key Metrics Stats Grid */}
-
- p.isMerged).length} Merged`} accent="var(--blue)" />
- i.state === 'closed').length} Closed`} accent="var(--amber)" />
- i.repository_url?.split('/').pop())).size}
- sub="distinct repositories"
+
+ p.isMerged).length} Merged`}
+ accent="var(--blue)"
+ />
+ i.state === 'closed').length} Closed`}
+ accent="var(--amber)"
+ />
+ i.repository_url?.split('/').pop())).size
+ }
+ sub={invalidDateRange ? '—' : 'distinct repositories'}
accent="var(--green)"
/>
@@ -569,6 +618,11 @@ export default function ContributorProfilePage() {
{
const status = p.state === 'open' ? 'Open' : p.isMerged ? 'Merged' : 'Closed'
const color = status === 'Merged' ? 'var(--green)' : status === 'Open' ? 'var(--blue)' : 'var(--text2)'
@@ -580,6 +634,11 @@ export default function ContributorProfilePage() {
{
const status = i.state === 'open' ? 'Open' : 'Closed'
const color = status === 'Open' ? 'var(--blue)' : 'var(--text2)'
diff --git a/src/utils/reportingDateRange.js b/src/utils/reportingDateRange.js
new file mode 100644
index 0000000..54ee067
--- /dev/null
+++ b/src/utils/reportingDateRange.js
@@ -0,0 +1,11 @@
+/**
+ * ISO date strings (YYYY-MM-DD) compare lexicographically as chronological order.
+ * Empty either side is treated as an open-ended (valid) range.
+ */
+export function isInvalidReportingRange(startDate, endDate) {
+ if (!startDate || !endDate) return false
+ return startDate > endDate
+}
+
+export const INVALID_REPORTING_RANGE_MESSAGE =
+ 'Invalid date range: Start date cannot be later than end date.'
diff --git a/src/utils/reportingDateRange.test.js b/src/utils/reportingDateRange.test.js
new file mode 100644
index 0000000..f19f95a
--- /dev/null
+++ b/src/utils/reportingDateRange.test.js
@@ -0,0 +1,30 @@
+import { describe, it, expect } from 'vitest'
+import {
+ isInvalidReportingRange,
+ INVALID_REPORTING_RANGE_MESSAGE,
+} from './reportingDateRange'
+
+describe('isInvalidReportingRange', () => {
+ it('returns false when either bound is empty (open-ended range)', () => {
+ expect(isInvalidReportingRange('', '2026-08-10')).toBe(false)
+ expect(isInvalidReportingRange('2026-08-20', '')).toBe(false)
+ expect(isInvalidReportingRange('', '')).toBe(false)
+ })
+
+ it('returns false when start is on or before end', () => {
+ expect(isInvalidReportingRange('2026-08-10', '2026-08-20')).toBe(false)
+ expect(isInvalidReportingRange('2026-08-10', '2026-08-10')).toBe(false)
+ })
+
+ it('returns true when start is later than end', () => {
+ expect(isInvalidReportingRange('2026-08-20', '2026-08-10')).toBe(true)
+ })
+})
+
+describe('INVALID_REPORTING_RANGE_MESSAGE', () => {
+ it('matches the expected contributor-profile copy', () => {
+ expect(INVALID_REPORTING_RANGE_MESSAGE).toBe(
+ 'Invalid date range: Start date cannot be later than end date.'
+ )
+ })
+})