Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/bounty/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { WorkSuggestion } from '@/types/search';
import { CommentEditor } from '@/components/Comment/CommentEditor';
import { JSONContent } from '@tiptap/core';
import { SessionProvider, useSession } from 'next-auth/react';
import { HubsSelector, Hub } from '@/app/paper/create/components/HubsSelector';
import { HubsSelector } from '@/app/paper/create/components/HubsSelector';
import { Currency } from '@/types/root';
import { BountyType } from '@/types/bounty';
import { useExchangeRate } from '@/contexts/ExchangeRateContext';
Expand Down Expand Up @@ -42,6 +42,7 @@ import { Icon } from '@/components/ui/icons/Icon';
import { ResearchCoinIcon } from '@/components/ui/icons/ResearchCoinIcon';
import { extractUserMentions } from '@/components/Comment/lib/commentUtils';
import { removeCommentDraftById } from '@/components/Comment/lib/commentDraftStorage';
import { IHub } from '@/types/hub';

// Wizard steps.
// We intentionally separate review-specific and answer-specific steps.
Expand Down Expand Up @@ -82,7 +83,7 @@ export default function CreateBountyPage() {
const [questionTitle, setQuestionTitle] = useState('');
const [questionPlainText, setQuestionPlainText] = useState<string>('');
const [questionHtml, setQuestionHtml] = useState<string>('');
const [selectedHubs, setSelectedHubs] = useState<Hub[]>([]);
const [selectedHubs, setSelectedHubs] = useState<IHub[]>([]);

// Shared – amount / currency
const [currency, setCurrency] = useState<Currency>('RSC');
Expand Down
11 changes: 6 additions & 5 deletions app/earn/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ import { EarnRightSidebar } from '@/components/Earn/EarnRightSidebar';
import { Coins } from 'lucide-react';
import { MainPageHeader } from '@/components/ui/MainPageHeader';
import Icon from '@/components/ui/icons/Icon';
import { BountyHubSelector as HubsSelector, Hub } from '@/components/Earn/BountyHubSelector';
import { BountyHubSelector as HubsSelector } from '@/components/Earn/BountyHubSelector';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
import { Badge } from '@/components/ui/Badge';
import { X } from 'lucide-react';
import { useClickContext } from '@/contexts/ClickContext';
import { IHub } from '@/types/hub';

export default function EarnPage() {
const [bounties, setBounties] = useState<FeedEntry[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [hasMore, setHasMore] = useState(false);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [selectedHubs, setSelectedHubs] = useState<Hub[]>([]);
const [selectedHubs, setSelectedHubs] = useState<IHub[]>([]);
const [sort, setSort] = useState<string>('personalized');

// Click context for topic filter
Expand All @@ -35,7 +36,7 @@ export default function EarnPage() {
{ label: 'RSC amount', value: '-total_amount' },
];

const fetchBounties = async (reset = false, hubs: Hub[] = selectedHubs) => {
const fetchBounties = async (reset = false, hubs: IHub[] = selectedHubs) => {
if (reset) {
// Clear current bounties so skeleton loaders show instead of stale data
setBounties([]);
Expand Down Expand Up @@ -76,7 +77,7 @@ export default function EarnPage() {
}
};

const handleHubsChange = (hubs: Hub[]) => {
const handleHubsChange = (hubs: IHub[]) => {
setSelectedHubs(hubs);
// Reset pagination and fetch bounties based on new hubs selection
setPage(1);
Expand Down Expand Up @@ -104,7 +105,7 @@ export default function EarnPage() {
useEffect(() => {
if (event && event.type === 'topic') {
const topic = event.payload;
const newHub: Hub = {
const newHub: IHub = {
id: topic.id,
name: topic.name,
description: topic.description,
Expand Down
90 changes: 80 additions & 10 deletions app/fund/components/FundPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,72 @@ import { GrantRightSidebar } from '@/components/Fund/GrantRightSidebar';
import { MainPageHeader } from '@/components/ui/MainPageHeader';
import { MarketplaceTabs, MarketplaceTab } from '@/components/Fund/MarketplaceTabs';
import Icon from '@/components/ui/icons/Icon';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
import { useState } from 'react';
import { FundingSelector } from '@/components/Fund/FundingSelector';
import { IHub } from '@/types/hub';

interface FundPageContentProps {
marketplaceTab: MarketplaceTab;
}

export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
const [selectedHubs, setSelectedHubs] = useState<IHub[]>([]);
const [selectedVotes, setSelectedVotes] = useState<number>(0);
const [selectedScore, setSelectedScore] = useState<number>(0);
const [selectedVerifiedAuthorsOnly, setSelectedVerifiedAuthorsOnly] = useState<boolean>(false);
const [selectedTaxDeductible, setSelectedTaxDeductible] = useState<boolean>(false);
const [selectedPreviouslyFunded, setSelectedPreviouslyFunded] = useState<boolean>(false);
const [sort, setSort] = useState<string>('amount_raised');

const getFundraiseStatus = (tab: MarketplaceTab): 'OPEN' | 'CLOSED' | undefined => {
if (tab === 'needs-funding') return 'OPEN';
if (tab === 'previously-funded') return 'CLOSED';
if (tab === 'needs-funding') {
return selectedPreviouslyFunded ? 'CLOSED' : 'OPEN';
}
return undefined;
};

const getOrdering = (tab: MarketplaceTab): string | undefined => {
if (tab === 'needs-funding') return 'amount_raised';
if (tab === 'needs-funding') return sort;
return undefined;
};

const getFiltering = (tab: MarketplaceTab): string | undefined => {
if (tab !== 'needs-funding') return undefined;
const filters = [];
if (selectedHubs.length > 0) {
const hubIds = selectedHubs.map((hub) => hub.id).join(',');
filters.push(`hub_ids=${hubIds}`);
}
if (selectedVotes > 0) {
filters.push(`min_upvotes=${selectedVotes}`);
}
if (selectedScore > 0) {
filters.push(`min_score=${selectedScore}`);
}
if (selectedVerifiedAuthorsOnly) {
filters.push(`verified_authors_only=true`);
}
if (selectedTaxDeductible) {
filters.push(`tax_deductible=true`);
}
return filters.length > 0 ? encodeURIComponent(filters.join('&')) : undefined;
};

const { entries, isLoading, hasMore, loadMore } = useFeed('all', {
contentType: marketplaceTab === 'grants' ? 'GRANT' : 'PREREGISTRATION',
endpoint: marketplaceTab === 'grants' ? 'grant_feed' : 'funding_feed',
fundraiseStatus: getFundraiseStatus(marketplaceTab),
ordering: getOrdering(marketplaceTab),
ordering: marketplaceTab === 'grants' ? undefined : getOrdering(marketplaceTab),
filtering: marketplaceTab === 'grants' ? undefined : getFiltering(marketplaceTab),
});

const getTitle = (tab: MarketplaceTab): string => {
switch (tab) {
case 'grants':
return 'Request for Proposals';
case 'needs-funding':
return 'Proposals';
case 'previously-funded':
return 'Previously Funded';
return selectedPreviouslyFunded ? 'Previously Funded' : 'Proposals';
default:
return '';
}
Expand All @@ -50,9 +84,9 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
case 'grants':
return 'Explore available funding opportunities';
case 'needs-funding':
return 'Fund breakthrough research shaping tomorrow';
case 'previously-funded':
return 'Browse research that has been successfully funded';
return selectedPreviouslyFunded
? 'Browse research that has been successfully funded'
: 'Fund breakthrough research shaping tomorrow';
default:
return '';
}
Expand All @@ -68,10 +102,46 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {

const rightSidebar = marketplaceTab === 'grants' ? <GrantRightSidebar /> : <FundRightSidebar />;

const sortOptions = [
{ label: 'Best', value: 'amount_raised' },
{ label: 'Newest', value: 'newest' },
{ label: 'Expiring soon', value: 'expiring_soon' },
{ label: 'Near goal', value: 'goal_percent' },
];

return (
<PageLayout rightSidebar={rightSidebar}>
{header}
<MarketplaceTabs activeTab={marketplaceTab} onTabChange={() => {}} />

{marketplaceTab === 'needs-funding' && (
<div className="flex items-center gap-0 sm:gap-2 flex-wrap justify-between">
<div className="w-1/2 sm:!w-[220px] flex-1 sm:!flex-none pr-1 sm:!pr-0">
<FundingSelector
selectedHubs={selectedHubs}
onHubsChange={setSelectedHubs}
selectedVotes={selectedVotes}
onVotesChange={setSelectedVotes}
selectedScore={selectedScore}
onScoreChange={setSelectedScore}
selectedVerifiedAuthorsOnly={selectedVerifiedAuthorsOnly}
onVerifiedAuthorsOnlyChange={setSelectedVerifiedAuthorsOnly}
selectedTaxDeductible={selectedTaxDeductible}
onTaxDeductibleChange={setSelectedTaxDeductible}
selectedPreviouslyFunded={selectedPreviouslyFunded}
onPreviouslyFundedChange={setSelectedPreviouslyFunded}
/>
</div>
<div className="w-1/2 sm:!w-[120px] flex-1 sm:!flex-none pl-1 sm:!pl-0">
<SortDropdown
value={sort}
onChange={(opt: SortOption) => setSort(opt.value)}
options={sortOptions}
/>
</div>
</div>
)}

<FeedContent
entries={entries}
isLoading={isLoading}
Expand Down
5 changes: 0 additions & 5 deletions app/fund/previously-funded/page.tsx

This file was deleted.

7 changes: 4 additions & 3 deletions app/paper/[id]/create/version/UploadVersionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import {
AuthorsAndAffiliations,
SelectedAuthor,
} from '@/app/paper/create/components/AuthorsAndAffiliations';
import { HubsSelector, Hub } from '@/app/paper/create/components/HubsSelector';
import { HubsSelector } from '@/app/paper/create/components/HubsSelector';
import { FileText, FileUp, Users, Tags, ArrowLeft, Info, MessageCircle } from 'lucide-react';
import { UploadFileResult } from '@/services/file.service';
import { PaperService } from '@/services/paper.service';
import toast from 'react-hot-toast';
import { Work } from '@/types/work';
import { WorkMetadata } from '@/services/metadata.service';
import { IHub } from '@/types/hub';

interface UploadVersionFormProps {
initialPaper: Work;
Expand Down Expand Up @@ -49,7 +50,7 @@ export default function UploadVersionForm({
isCorrespondingAuthor: auth.isCorresponding,
}))
);
const [selectedHubs, setSelectedHubs] = useState<Hub[]>(() => {
const [selectedHubs, setSelectedHubs] = useState<IHub[]>(() => {
const sourceTopics = metadata?.topics ?? initialPaper.topics;
return sourceTopics.map((topic) => ({
id: topic.id,
Expand Down Expand Up @@ -117,7 +118,7 @@ export default function UploadVersionForm({
}
};

const handleHubsChange = (newHubs: Hub[]) => {
const handleHubsChange = (newHubs: IHub[]) => {
setSelectedHubs(newHubs);
if (errors.hubs) {
setErrors({ ...errors, hubs: null });
Expand Down
18 changes: 6 additions & 12 deletions app/paper/create/components/HubsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ import { X, ChevronDown, Filter } from 'lucide-react';
import { BaseMenu } from '@/components/ui/form/BaseMenu';
import { HubService } from '@/services/hub.service';
import { Topic } from '@/types/topic';

export interface Hub {
id: string | number;
name: string;
description?: string;
color?: string;
}
import { IHub } from '@/types/hub';

interface HubsSelectorProps {
selectedHubs: Hub[];
onChange: (hubs: Hub[]) => void;
selectedHubs: IHub[];
onChange: (hubs: IHub[]) => void;
error?: string | null;
displayCountOnly?: boolean;
hideSelectedItems?: boolean;
Expand All @@ -35,15 +29,15 @@ export function HubsSelector({
hideSelectedItems = false,
}: HubsSelectorProps) {
// Convert hubs to the format expected by SearchableMultiSelect
const hubsToOptions = (hubs: Hub[]): MultiSelectOption[] => {
const hubsToOptions = (hubs: IHub[]): MultiSelectOption[] => {
return hubs.map((hub) => ({
value: String(hub.id),
label: hub.name,
}));
};

// Convert Topic to Hub
const topicsToHubs = (topics: Topic[]): Hub[] => {
const topicsToHubs = (topics: Topic[]): IHub[] => {
return topics.map((topic) => ({
id: topic.id,
name: topic.name,
Expand All @@ -52,7 +46,7 @@ export function HubsSelector({
};

// Convert MultiSelectOption back to Hub objects
const optionsToHubs = (options: MultiSelectOption[]): Hub[] => {
const optionsToHubs = (options: MultiSelectOption[]): IHub[] => {
return options.map((option) => {
// Find the original hub in the selectedHubs array
const existingHub = selectedHubs.find((hub) => String(hub.id) === option.value);
Expand Down
7 changes: 4 additions & 3 deletions app/paper/create/pdf/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Input } from '@/components/ui/form/Input';
import { FileUpload } from '@/components/ui/form/FileUpload';
import { SimpleStepProgress, SimpleStep } from '@/components/ui/SimpleStepProgress';
import { AuthorsAndAffiliations, SelectedAuthor } from '../components/AuthorsAndAffiliations';
import { HubsSelector, Hub } from '../components/HubsSelector';
import { HubsSelector } from '../components/HubsSelector';
import { DeclarationCheckbox } from '../components/DeclarationCheckbox';
import {
ArrowLeft,
Expand All @@ -30,6 +30,7 @@ import { Switch } from '@/components/ui/Switch';
import { AvatarStack } from '@/components/ui/AvatarStack';
import { useScreenSize } from '@/hooks/useScreenSize';
import { Callout } from '@/components/ui/Callout';
import { IHub } from '@/types/hub';

// Define the steps of our flow
const steps: SimpleStep[] = [
Expand All @@ -52,7 +53,7 @@ export default function UploadPDFPage() {
const [abstract, setAbstract] = useState('');
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [authors, setAuthors] = useState<SelectedAuthor[]>([]);
const [selectedHubs, setSelectedHubs] = useState<Hub[]>([]);
const [selectedHubs, setSelectedHubs] = useState<IHub[]>([]);
const [changeDescription, setChangeDescription] = useState('Initial submission');
const [fileUploadResult, setFileUploadResult] = useState<UploadFileResult | null>(null);

Expand Down Expand Up @@ -151,7 +152,7 @@ export default function UploadPDFPage() {
}
};

const handleHubsChange = (newHubs: Hub[]) => {
const handleHubsChange = (newHubs: IHub[]) => {
setSelectedHubs(newHubs);
if (errors.hubs) {
setErrors({ ...errors, hubs: null });
Expand Down
17 changes: 16 additions & 1 deletion components/Comment/lib/ReviewExtension.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use client';

import { Node, Extension, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react';
import React from 'react';
import { Star } from 'lucide-react';
import { Ban, Star } from 'lucide-react';
import { useState } from 'react';

// Common ReviewStars component used by both overall and section ratings
Expand All @@ -10,12 +12,14 @@ export const ReviewStars = ({
onRatingChange,
isRequired = false,
isReadOnly = false,
isClearable = false,
label = 'Overall rating:',
}: {
rating: number;
onRatingChange: (rating: number) => void;
isRequired?: boolean;
isReadOnly?: boolean;
isClearable?: boolean;
label?: string;
}) => {
const [hoverRating, setHoverRating] = useState(0);
Expand All @@ -41,6 +45,17 @@ export const ReviewStars = ({
<Star className="h-5 w-5 fill-current" />
</button>
))}
{isClearable && (
<button
type="button"
onClick={() => onRatingChange(0)}
className={`pl-2 cursor-pointer hover:text-red-500 transition-colors ${
rating ? 'text-red-200' : 'text-gray-300'
}`}
>
<Ban className="h-5 w-5" />
</button>
)}
</div>
</div>
);
Expand Down
Loading