From e1549b08dd94548792450ab9e6a84a3b83278e16 Mon Sep 17 00:00:00 2001 From: PoornaChandra2005 Date: Tue, 10 Feb 2026 06:41:46 +0530 Subject: [PATCH 1/3] fix: bounty display, metadata improvements, and RHF logic --- components/Bounty/BountyMetadataLine.tsx | 36 +++- components/Bounty/lib/bountyUtil.ts | 1 + components/Feed/FeedItemActions.tsx | 174 ++++++++---------- .../banners/EarningOpportunityBanner.tsx | 2 +- types/bounty.ts | 3 +- types/user.ts | 2 + 6 files changed, 111 insertions(+), 107 deletions(-) diff --git a/components/Bounty/BountyMetadataLine.tsx b/components/Bounty/BountyMetadataLine.tsx index 5a97ca053..d919b3e6c 100644 --- a/components/Bounty/BountyMetadataLine.tsx +++ b/components/Bounty/BountyMetadataLine.tsx @@ -2,8 +2,9 @@ import { formatDeadline } from '@/utils/date'; import { CurrencyBadge } from '@/components/ui/CurrencyBadge'; import { RadiatingDot } from '@/components/ui/RadiatingDot'; import { ContentTypeBadge } from '@/components/ui/ContentTypeBadge'; -import { Check } from 'lucide-react'; +import { Check, XCircle } from 'lucide-react'; import { useCurrencyPreference } from '@/contexts/CurrencyPreferenceContext'; +import { BountyStatus } from '@/types/bounty'; interface BountyMetadataLineProps { amount: number; @@ -13,7 +14,7 @@ interface BountyMetadataLineProps { className?: string; solutionsCount?: number; showDeadline?: boolean; - bountyStatus?: 'OPEN' | 'CLOSED' | 'ASSESSMENT'; + bountyStatus?: BountyStatus; /** * If true, the amount is already in the target currency and should not be converted. * Useful when the caller has pre-calculated the amount (e.g., Foundation bounty flat fee). @@ -37,11 +38,17 @@ export const BountyMetadataLine = ({ const deadlineText = bountyStatus === 'ASSESSMENT' ? 'Assessment Period' - : isOpen - ? expirationDate - ? formatDeadline(expirationDate) - : 'No deadline' - : 'Completed'; + : bountyStatus === 'EXPIRED' + ? 'Expired' + : bountyStatus === 'CANCELLED' + ? 'Cancelled' + : isOpen + ? expirationDate + ? formatDeadline(expirationDate) + : 'No deadline' + : 'Completed'; + + const isInactive = bountyStatus === 'EXPIRED' || bountyStatus === 'CANCELLED'; return (
@@ -49,13 +56,14 @@ export const BountyMetadataLine = ({
{/* Badges */}
- +
@@ -63,11 +71,21 @@ export const BountyMetadataLine = ({
{isOpen ? ( + ) : isInactive ? ( + ) : ( )} {deadlineText} diff --git a/components/Bounty/lib/bountyUtil.ts b/components/Bounty/lib/bountyUtil.ts index d5cafce2d..466d5bd43 100644 --- a/components/Bounty/lib/bountyUtil.ts +++ b/components/Bounty/lib/bountyUtil.ts @@ -523,6 +523,7 @@ const getCreatorUserId = (bounty: Bounty): number | undefined => { * @returns True if the bounty was created by the Foundation account */ export const isFoundationBounty = (bounty: Bounty): boolean => { + if (bounty.createdBy?.isOfficialAccount) return true; if (!FOUNDATION_USER_ID) return false; const creatorUserId = getCreatorUserId(bounty); diff --git a/components/Feed/FeedItemActions.tsx b/components/Feed/FeedItemActions.tsx index 66a7391f1..6a2e06a51 100644 --- a/components/Feed/FeedItemActions.tsx +++ b/components/Feed/FeedItemActions.tsx @@ -415,14 +415,16 @@ export const FeedItemActions: FC = ({ const showInlineReviews = showPeerReviews && reviews.length > 0; const showInlineBounties = hasOpenBounties; - // Calculate total awarded amount (tips + bounty awards) + // Calculate tip amount and awarded bounty amount separately const tipAmount = tips.reduce((total, tip) => total + (tip.amount || 0), 0); const totalAwarded = tipAmount + (awardedBountyAmount || 0); + const hasBountyAwards = (awardedBountyAmount || 0) > 0; return ( <>
+ {/* ... voting buttons ... */}
= ({ showTooltip={showTooltips} /> )} - {(onTip || totalAwarded > 0) && - (showTooltips && totalAwarded > 0 ? ( - - } - position="top" - width="w-[320px]" - > - {onTip ? ( - - ) : ( -
- - {totalAwarded > 0 ? ( - - {formatCurrency({ - amount: totalAwarded, - showUSD, - exchangeRate, - shorten: true, - })} - - ) : ( - Tip - )} -
- )} -
- ) : onTip ? ( - - ) : ( + + ) : ( +
+ + + {formatCurrency({ amount: tipAmount, showUSD, exchangeRate, shorten: true })} + +
+ )} + + )} + + {/* Bounty Awarded Badge */} + {hasBountyAwards && ( + +

Bounty Awarded

+

Total amount awarded from bounties on this document.

+
+ } + position="top" + width="w-[240px]" + disabled={!showTooltips} + >
- - {totalAwarded > 0 ? ( - - {formatCurrency({ amount: totalAwarded, showUSD, exchangeRate, shorten: true })} - - ) : ( - Tip - )} + + + {formatCurrency({ + amount: awardedBountyAmount || 0, + showUSD, + exchangeRate, + shorten: true, + })} + {!showUSD && ' RSC'} +
- ))} + + )} {showInlineReviews && (showTooltips && reviews.length > 0 ? ( { moderator: raw.moderator || false, editorOfHubs: editorOfHubs, isModerator: raw.moderator || false, + isOfficialAccount: raw.is_official_account || false, referralCode: raw.referral_code || undefined, authProvider: raw.auth_provider ? raw.auth_provider === 'google' From 76c38bb43fcfd50c0506b7175ba4d9c4bc2a7e24 Mon Sep 17 00:00:00 2001 From: PoornaChandra2005 Date: Tue, 10 Feb 2026 12:56:33 +0530 Subject: [PATCH 2/3] refactor: simplify bounty metadata logic and cleanup feed actions --- components/Bounty/BountyMetadataLine.tsx | 68 ++++++++++++------------ components/Feed/FeedItemActions.tsx | 17 +++++- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/components/Bounty/BountyMetadataLine.tsx b/components/Bounty/BountyMetadataLine.tsx index d919b3e6c..b9c76d047 100644 --- a/components/Bounty/BountyMetadataLine.tsx +++ b/components/Bounty/BountyMetadataLine.tsx @@ -34,22 +34,42 @@ export const BountyMetadataLine = ({ }: BountyMetadataLineProps) => { const { showUSD } = useCurrencyPreference(); - // Format the deadline text - const deadlineText = - bountyStatus === 'ASSESSMENT' - ? 'Assessment Period' - : bountyStatus === 'EXPIRED' - ? 'Expired' - : bountyStatus === 'CANCELLED' - ? 'Cancelled' - : isOpen - ? expirationDate - ? formatDeadline(expirationDate) - : 'No deadline' - : 'Completed'; + // Helper to determine the deadline text + const getDeadlineText = () => { + if (bountyStatus === 'ASSESSMENT') return 'Assessment Period'; + if (bountyStatus === 'EXPIRED') return 'Expired'; + if (bountyStatus === 'CANCELLED') return 'Cancelled'; + if (isOpen) { + return expirationDate ? formatDeadline(expirationDate) : 'No deadline'; + } + return 'Completed'; + }; + const deadlineText = getDeadlineText(); const isInactive = bountyStatus === 'EXPIRED' || bountyStatus === 'CANCELLED'; + // Helper to determine the status icon + const renderStatusIcon = () => { + if (isOpen) { + return ; + } + if (isInactive) { + return ; + } + return ; + }; + + // Helper to determine the status text color + const getStatusColorClass = () => { + if (isOpen) { + return expiringSoon ? 'text-orange-600 font-medium' : 'text-gray-700'; + } + if (isInactive) { + return 'text-gray-500 italic'; + } + return 'text-green-700 font-medium'; + }; + return (
{/* Badges and date in one row */} @@ -69,26 +89,8 @@ export const BountyMetadataLine = ({ {showDeadline && (
- {isOpen ? ( - - ) : isInactive ? ( - - ) : ( - - )} - - {deadlineText} - + {renderStatusIcon()} + {deadlineText}
)}
diff --git a/components/Feed/FeedItemActions.tsx b/components/Feed/FeedItemActions.tsx index 6a2e06a51..65482c8d5 100644 --- a/components/Feed/FeedItemActions.tsx +++ b/components/Feed/FeedItemActions.tsx @@ -146,8 +146,10 @@ interface FeedItemActionsProps { comment?: string; upvote?: string; report?: string; + award?: string; }; onComment?: () => void; + onAward?: () => void; // Prop for inline award action onTip?: () => void; // Callback for tip action (when provided, tip button shows) children?: ReactNode; // Add children prop to accept additional action buttons showTooltips?: boolean; // New property for controlling tooltips @@ -201,6 +203,7 @@ export const FeedItemActions: FC = ({ reviews = [], bounties = [], awardedBountyAmount, + onAward, tips = [], relatedDocumentTopics, relatedDocumentUnifiedDocumentId, @@ -417,7 +420,6 @@ export const FeedItemActions: FC = ({ // Calculate tip amount and awarded bounty amount separately const tipAmount = tips.reduce((total, tip) => total + (tip.amount || 0), 0); - const totalAwarded = tipAmount + (awardedBountyAmount || 0); const hasBountyAwards = (awardedBountyAmount || 0) > 0; return ( @@ -661,6 +663,19 @@ export const FeedItemActions: FC = ({ onClick={handleBountyClick} /> ))} + + {/* Inline Award Button - For bounty creators to award specific comments */} + {feedContentType === 'COMMENT' && onAward && ( + + )} + {children}
From c2d4e0af9d04c51540051b909882a8a01cb10671 Mon Sep 17 00:00:00 2001 From: PoornaChandra2005 Date: Tue, 10 Feb 2026 16:08:26 +0530 Subject: [PATCH 3/3] feat: add Foundation vs Community bounty filter to Earn tab --- app/earn/page.tsx | 34 ++++++++++++++++++++++++++-------- hooks/useBounties.ts | 21 ++++++++++++++++++++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/app/earn/page.tsx b/app/earn/page.tsx index c893e093a..984fe46c5 100644 --- a/app/earn/page.tsx +++ b/app/earn/page.tsx @@ -19,6 +19,8 @@ export default function EarnPage() { loadMore, sort, handleSortChange, + bountyFilter, + handleBountyFilterChange, selectedHubs, handleHubsChange, restoredScrollPosition, @@ -34,19 +36,35 @@ export default function EarnPage() { { label: 'RSC amount', value: '-total_amount' }, ]; + // Available bounty type options + const bountyTypeOptions = [ + { label: 'All Bounties', value: 'ALL' }, + { label: 'Foundation Only', value: 'FOUNDATION' }, + { label: 'Community Only', value: 'COMMUNITY' }, + ]; + const renderFilters = () => (
{/* Top filter bar */}
-
- +
+
+ +
+
+ handleBountyFilterChange(opt.value as any)} + options={bountyTypeOptions} + /> +
-
+
handleSortChange(opt.value)} diff --git a/hooks/useBounties.ts b/hooks/useBounties.ts index 14f11c157..ad447c49d 100644 --- a/hooks/useBounties.ts +++ b/hooks/useBounties.ts @@ -67,6 +67,7 @@ export const useBounties = () => { const [selectedHubs, setSelectedHubs] = useState([]); const selectedHubsRef = useRef([]); const [sort, setSort] = useState(sortFromUrl); + const [bountyFilter, setBountyFilter] = useState<'ALL' | 'FOUNDATION' | 'COMMUNITY'>('ALL'); const previousHubsParamRef = useRef(''); const hasInitialFetchRef = useRef(false); const previousSortRef = useRef(sortFromUrl); @@ -236,6 +237,22 @@ export const useBounties = () => { router.replace(`?${params.toString()}`, { scroll: false }); }; + const handleBountyFilterChange = (filter: 'ALL' | 'FOUNDATION' | 'COMMUNITY') => { + setBountyFilter(filter); + }; + + const filteredEntries = useMemo(() => { + if (bountyFilter === 'ALL') return entries; + + return entries.filter((entry) => { + const bounties = entry.content.bounties || []; + const hasFoundation = bounties.some((b) => b.createdBy?.isOfficialAccount); + if (bountyFilter === 'FOUNDATION') return hasFoundation; + if (bountyFilter === 'COMMUNITY') return !hasFoundation; + return true; + }); + }, [entries, bountyFilter]); + const loadMore = () => { fetchBounties(); }; @@ -294,12 +311,14 @@ export const useBounties = () => { }, [event, router, searchParams]); return { - entries, + entries: filteredEntries, isLoading, hasMore, loadMore, sort, handleSortChange, + bountyFilter, + handleBountyFilterChange, selectedHubs, handleHubsChange, restoredScrollPosition,