From 2785092a635e885fac0da599d1128da2a3146d74 Mon Sep 17 00:00:00 2001 From: michaelcanova Date: Sun, 9 Aug 2026 15:48:50 -0400 Subject: [PATCH 1/2] [Funding] Component Cleanup --- app/fund/dashboard/FunderDashboardContent.tsx | 33 ++++++++++ app/fund/dashboard/page.tsx | 6 +- ...FundsReceivedTab.tsx => FundsReceived.tsx} | 4 +- app/my-funding/components/MyFundingPage.tsx | 11 ++-- ...FunderDashboardPage.tsx => FundsGiven.tsx} | 65 +++++-------------- 5 files changed, 62 insertions(+), 57 deletions(-) create mode 100644 app/fund/dashboard/FunderDashboardContent.tsx rename app/my-funding/components/{FundsReceivedTab.tsx => FundsReceived.tsx} (97%) rename components/Funding/dashboard/{FunderDashboardPage.tsx => FundsGiven.tsx} (70%) diff --git a/app/fund/dashboard/FunderDashboardContent.tsx b/app/fund/dashboard/FunderDashboardContent.tsx new file mode 100644 index 000000000..60d4e159b --- /dev/null +++ b/app/fund/dashboard/FunderDashboardContent.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { FundsGiven } from '@/components/Funding/dashboard/FundsGiven'; +import { useUser } from '@/contexts/UserContext'; + +export function FunderDashboardContent() { + const router = useRouter(); + const { user, isLoading: isLoadingUser } = useUser(); + + useEffect(() => { + if (!isLoadingUser && !user) { + router.replace('/'); + } + }, [isLoadingUser, router, user]); + + if (isLoadingUser || !user) return null; + + const firstName = user.firstName?.trim(); + + return ( + <> +
+

+ {firstName ? `Welcome back, ${firstName}.` : 'Welcome back.'} +

+

Here's where your funding stands today.

+
+ + + ); +} diff --git a/app/fund/dashboard/page.tsx b/app/fund/dashboard/page.tsx index 1793f01c0..a2c325435 100644 --- a/app/fund/dashboard/page.tsx +++ b/app/fund/dashboard/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from 'next'; import { PageLayout } from '@/app/layouts/PageLayout'; -import { FunderDashboardPage } from '@/components/Funding/dashboard/FunderDashboardPage'; import { buildOpenGraphMetadata } from '@/lib/metadata'; +import { FunderDashboardContent } from './FunderDashboardContent'; export const metadata: Metadata = buildOpenGraphMetadata({ title: 'Funder Dashboard', @@ -11,8 +11,8 @@ export const metadata: Metadata = buildOpenGraphMetadata({ export default function FunderDashboardRoute() { return ( - - + + ); } diff --git a/app/my-funding/components/FundsReceivedTab.tsx b/app/my-funding/components/FundsReceived.tsx similarity index 97% rename from app/my-funding/components/FundsReceivedTab.tsx rename to app/my-funding/components/FundsReceived.tsx index 3f87597a8..0e8dee0fe 100644 --- a/app/my-funding/components/FundsReceivedTab.tsx +++ b/app/my-funding/components/FundsReceived.tsx @@ -14,7 +14,7 @@ import { transformContributionToFeedEntry } from '@/types/contribution'; import type { FeedEntry } from '@/types/feed'; import { cn } from '@/utils/styles'; -interface FundsReceivedTabProps { +interface FundsReceivedProps { userId: number; authorId?: number; } @@ -164,7 +164,7 @@ function PeerReviews({ authorId }: Readonly<{ authorId?: number }>) { ); } -export function FundsReceivedTab({ userId, authorId }: Readonly) { +export function FundsReceived({ userId, authorId }: Readonly) { const router = useRouter(); const browsePeerReviewBounties = () => { diff --git a/app/my-funding/components/MyFundingPage.tsx b/app/my-funding/components/MyFundingPage.tsx index 5d2d722bd..c04adf83c 100644 --- a/app/my-funding/components/MyFundingPage.tsx +++ b/app/my-funding/components/MyFundingPage.tsx @@ -4,11 +4,11 @@ import { useEffect, type ReactNode } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { ArrowDownLeft, ArrowUpRight } from 'lucide-react'; import { PageLayout } from '@/app/layouts/PageLayout'; -import { FunderDashboardPage } from '@/components/Funding/dashboard/FunderDashboardPage'; +import { FundsGiven } from '@/components/Funding/dashboard/FundsGiven'; import { Icon } from '@/components/ui/icons/Icon'; import { Tabs } from '@/components/ui/Tabs'; import { useUser } from '@/contexts/UserContext'; -import { FundsReceivedTab } from './FundsReceivedTab'; +import { FundsReceived } from './FundsReceived'; type MyFundingTab = 'given' | 'received'; @@ -121,8 +121,9 @@ export function MyFundingPage() { const searchParams = useSearchParams(); const { user, isLoading: isLoadingUser } = useUser(); const activeTab = resolveMyFundingTab(searchParams.get('tab')); + const isModerator = !!user?.isModerator; const hasModeratorOverrideOnReceivedTab = - activeTab === 'received' && user?.isModerator === true && searchParams.has('funder_id'); + activeTab === 'received' && isModerator && searchParams.has('funder_id'); useEffect(() => { if (isLoadingUser) return; @@ -153,9 +154,9 @@ export function MyFundingPage() { return ( }> {activeTab === 'given' ? ( - + ) : ( - + )} ); diff --git a/components/Funding/dashboard/FunderDashboardPage.tsx b/components/Funding/dashboard/FundsGiven.tsx similarity index 70% rename from components/Funding/dashboard/FunderDashboardPage.tsx rename to components/Funding/dashboard/FundsGiven.tsx index f2d598e38..1ca71e8aa 100644 --- a/components/Funding/dashboard/FunderDashboardPage.tsx +++ b/components/Funding/dashboard/FundsGiven.tsx @@ -1,6 +1,6 @@ 'use client'; -import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { Plus } from 'lucide-react'; import { Button } from '@/components/ui/Button'; @@ -10,39 +10,30 @@ import { FundedProposalsSection } from '@/components/Funding/dashboard/FundedPro import { FeedContent } from '@/components/Feed/FeedContent'; import { FunderService } from '@/services/funder.service'; import { useFeed } from '@/hooks/useFeed'; -import { useUser } from '@/contexts/UserContext'; import { FunderOverview } from '@/types/funder'; import { SearchableUserSingleSelect, UserOption, } from '@/components/ui/form/SearchableUserSingleSelect'; -function parseFunderIdParam(raw: string | null): number | undefined { - if (!raw) return undefined; - const n = Number(raw); - return Number.isFinite(n) && n > 0 ? n : undefined; +function parseFunderIdParam(funderIdParam: string | null): number | undefined { + if (!funderIdParam) return undefined; + const funderId = Number(funderIdParam); + return Number.isFinite(funderId) && funderId > 0 ? funderId : undefined; } -interface FunderDashboardPageProps { - embedded?: boolean; +interface FundsGivenProps { + userId: number; + isModerator: boolean; } -export const FunderDashboardPage: FC = ({ embedded = false }) => { +export function FundsGiven({ userId, isModerator }: Readonly) { const router = useRouter(); const searchParams = useSearchParams(); - const { user, isLoading: isLoadingUser } = useUser(); - const userId = user?.id; - useEffect(() => { - if (!isLoadingUser && !user) { - router.replace('/'); - } - }, [isLoadingUser, user, router]); - - const funderIdOverride = user?.isModerator - ? parseFunderIdParam(searchParams.get('funder_id')) - : undefined; - const funderId = funderIdOverride ?? userId; + const funderId = isModerator + ? (parseFunderIdParam(searchParams.get('funder_id')) ?? userId) + : userId; const [selectedUser, setSelectedUser] = useState(null); @@ -67,13 +58,7 @@ export const FunderDashboardPage: FC = ({ embedded = f () => ({ endpoint: 'grant_feed' as const, contentType: 'GRANT', - // The override `funderId` is resolved at the call site and passed in - // as `created_by` so we don't have to duplicate the override logic in - // the lower-level services. createdBy: funderId, - // Defer the initial fetch until funderId is known so we don't fire a - // first request without `created_by` and a second with it. - enabled: funderId != null, }), [funderId] ); @@ -86,7 +71,6 @@ export const FunderDashboardPage: FC = ({ embedded = f } = useFeed('all', grantFeedOptions); useEffect(() => { - if (isLoadingUser || !user) return; let cancelled = false; setIsLoadingOverview(true); FunderService.getFundingOverview(funderId) @@ -102,15 +86,11 @@ export const FunderDashboardPage: FC = ({ embedded = f return () => { cancelled = true; }; - }, [funderId, isLoadingUser, user]); - - if (isLoadingUser || !user) return null; - - const firstName = user.firstName?.trim(); + }, [funderId]); return ( -
- {user.isModerator && ( + <> + {isModerator && (
)} - {!embedded && ( -
-

- {firstName ? `Welcome back, ${firstName}.` : 'Welcome back.'} -

-

Here's where your funding stands today.

-
- )} - {isLoadingOverview ? (
) : overview ? ( ) : null} - {funderId && } +
@@ -194,6 +165,6 @@ export const FunderDashboardPage: FC = ({ embedded = f {overview && ( )} -
+ ); -}; +} From 4b3106a0302b1c98c86138361a5f564f228af1a4 Mon Sep 17 00:00:00 2001 From: michaelcanova Date: Sun, 9 Aug 2026 16:14:28 -0400 Subject: [PATCH 2/2] Linter Fixes --- components/Funding/dashboard/FundsGiven.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/components/Funding/dashboard/FundsGiven.tsx b/components/Funding/dashboard/FundsGiven.tsx index 1ca71e8aa..b5f89d615 100644 --- a/components/Funding/dashboard/FundsGiven.tsx +++ b/components/Funding/dashboard/FundsGiven.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { Plus } from 'lucide-react'; import { Button } from '@/components/ui/Button'; @@ -88,13 +88,20 @@ export function FundsGiven({ userId, isModerator }: Readonly) { }; }, [funderId]); + let overviewContent: ReactNode = null; + if (isLoadingOverview) { + overviewContent = ( +
+ ); + } else if (overview) { + overviewContent = ; + } + return ( <> {isModerator && (
- +

View as user (moderator only)

) {
)} - {isLoadingOverview ? ( -
- ) : overview ? ( - - ) : null} + {overviewContent}