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
60 changes: 3 additions & 57 deletions app/author/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import {
type ActivityPillId,
} from '@/components/profile/ProfileActivityTab';
import { OrcidSyncBanner } from '@/components/profile/OrcidSyncBanner';
import { useAuthorPublications } from '@/hooks/usePublications';
import { transformPublicationToFeedEntry } from '@/types/publication';
import PinnedFundraise from './components/PinnedFundraise';
import { useOrcidCallback } from '@/components/Orcid/lib/hooks/useOrcidCallback';
import {
Expand All @@ -52,7 +50,6 @@ function AuthorProfileError({ error }: { error: string }) {

const TAB_TO_CONTRIBUTION_TYPE: Record<string, ContributionType> = {
contributions: 'ALL',
publications: 'ARTICLE',
'peer-reviews': 'REVIEW',
comments: 'CONVERSATION',
bounties: 'BOUNTY',
Expand Down Expand Up @@ -119,59 +116,6 @@ function AuthorTabContent({
? allContributions.filter((contribution) => !contribution.item?.review?.score)
: allContributions;

const {
publications,
isLoading: isPublicationsLoading,
error: publicationsError,
hasMore: hasMorePublications,
loadMore: loadMorePublications,
isLoadingMore: isLoadingMorePublications,
restoredFeedEntries: restoredPublicationsEntries,
restoredScrollPosition: restoredPublicationsScrollPosition,
lastClickedEntryId: lastClickedPublicationsEntryId,
} = useAuthorPublications({
authorId,
activeTab: currentTab,
});

if (currentTab === 'publications') {
if (publicationsError) {
return <div>Error: {publicationsError.message}</div>;
}

const entries =
restoredPublicationsEntries ||
publications
.map((publication) => {
try {
return transformPublicationToFeedEntry(publication);
} catch (error) {
console.warn('[Publication] Could not parse publication', error);
return null;
}
})
.filter((entry): entry is FeedEntry => !!entry);

return (
<FeedContent
entries={isPending ? [] : entries}
isLoading={isPending || isPublicationsLoading}
hasMore={hasMorePublications}
loadMore={loadMorePublications}
showBountyFooter={false}
hideActions={true}
isLoadingMore={isLoadingMorePublications}
noEntriesElement={<SearchEmpty title="No publications found." className="mb-10" />}
maxLength={150}
activeTab={currentTab}
restoredScrollPosition={restoredPublicationsScrollPosition}
lastClickedEntryId={lastClickedPublicationsEntryId ?? undefined}
shouldRenderBountyAsComment={true}
wideContent
/>
);
}

if (contributionsError) {
return <div>Error: {contributionsError.message}</div>;
}
Expand Down Expand Up @@ -359,7 +303,9 @@ export default function AuthorProfilePage({ params }: { params: Promise<{ id: st
}

if (activeGroup === 'activity') {
const activePill: ActivityPillId = isActivityPill(currentTab) ? currentTab : 'publications';
const activePill: ActivityPillId = isActivityPill(currentTab)
? currentTab
: ACTIVITY_PILLS[0].id;
return (
<ProfileActivityTab activePill={activePill} onPillChange={setTab} userId={author.userId}>
<AuthorTabContent
Expand Down
10 changes: 1 addition & 9 deletions components/modals/Verification/AddPublicationsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { WS_ROUTES } from '@/services/websocket';
import { transformNotification } from '@/types/notification';
import { Button } from '@/components/ui/Button';
import { Info, ArrowLeft, Check, ChevronDown } from 'lucide-react';
import { toast } from 'react-hot-toast';
import { Input } from '@/components/ui/form/Input';
import { Spinner } from '@/components/Editor/components/ui/Spinner';
import { Dropdown, DropdownItem } from '@/components/ui/form/Dropdown';
Expand Down Expand Up @@ -136,13 +135,6 @@ export function AddPublicationsForm({
}
};

const handleDoThisLater = () => {
onDoThisLater?.();
toast.success('Visit the "Publications" tab on your profile to resume', {
duration: 5000,
});
};

const handleAddPublications = async () => {
setStep('LOADING');
try {
Expand Down Expand Up @@ -200,7 +192,7 @@ export function AddPublicationsForm({

<div className="flex justify-between items-center mt-8">
{allowDoThisLater && (
<Button variant="ghost" onClick={handleDoThisLater} className="text-primary">
<Button variant="ghost" onClick={onDoThisLater} className="text-primary">
Do this later
</Button>
)}
Expand Down
22 changes: 15 additions & 7 deletions components/profile/ProfileActivityTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { PillTabs } from '@/components/ui/PillTabs';
import { useFeed } from '@/hooks/useFeed';

export const ACTIVITY_PILLS = [
{ id: 'publications', label: 'Publications' },
{ id: 'proposals', label: 'Proposals' },
{ id: 'peer-reviews', label: 'Peer Reviews' },
{ id: 'comments', label: 'Comments' },
Expand All @@ -25,6 +24,14 @@ interface ProfileActivityTabProps {
children: React.ReactNode;
}

function ProposalsEmptyState() {
return (
<div className="py-12 text-center">
<p className="text-gray-400 text-sm">No proposals yet</p>
</div>
);
}

function ProposalsContent({ userId }: { userId: number }) {
const { entries, isLoading, hasMore, loadMore } = useFeed('all', {
endpoint: 'funding_feed',
Expand All @@ -45,11 +52,7 @@ function ProposalsContent({ userId }: { userId: number }) {
hideActions
skeletonVariant="fundraise"
wideContent
noEntriesElement={
<div className="py-12 text-center">
<p className="text-gray-400 text-sm">No proposals yet</p>
</div>
}
noEntriesElement={<ProposalsEmptyState />}
/>
);
}
Expand All @@ -65,6 +68,11 @@ export function ProfileActivityTab({
userId,
children,
}: ProfileActivityTabProps) {
let activityContent = children;
if (activePill === 'proposals') {
activityContent = userId ? <ProposalsContent userId={userId} /> : <ProposalsEmptyState />;
}

return (
<div>
<div className="mb-4">
Expand All @@ -75,7 +83,7 @@ export function ProfileActivityTab({
size="sm"
/>
</div>
{activePill === 'proposals' && userId ? <ProposalsContent userId={userId} /> : children}
{activityContent}
</div>
);
}
1 change: 0 additions & 1 deletion hooks/useContributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const useContributions = (options: UseContributionsOptions = {}) => {
useFeedStateRestoration({
activeTab: options.activeTab,
shouldRestore: (isBackNavigation) => {
if (options.contribution_type === 'ARTICLE') return false;
if (!isBackNavigation || !options.author_id) return false;
return true;
},
Expand Down
2 changes: 1 addition & 1 deletion hooks/useFeedSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { FeedSource } from '@/types/analytics';
* - /topic/ai/latest → source: 'topic', tab: 'latest'
* - /fund/needs-funding → source: 'fund', tab: 'needs-funding'
* - /author/153397 → source: 'author', tab: 'contributions'
* - /author/153397?tab=publications → source: 'author', tab: 'publications'
* - /author/153397?tab=peer-reviews → source: 'author', tab: 'peer-reviews'
* - /list/123 → source: 'list', tab: '123'
* - /search?q=ai → source: 'search', tab: 'search'
*/
Expand Down
104 changes: 1 addition & 103 deletions hooks/usePublications.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
'use client';

import { useState, useCallback, useEffect } from 'react';
import { useState, useCallback } from 'react';
import {
PublicationService,
PublicationSearchParams,
PublicationError,
AddPublicationsParams,
AuthorPublicationsResponse,
} from '@/services/publication.service';
import { OpenAlexWork, OpenAlexAuthor, PublicationSearchResponse } from '@/types/publication';
import { ID } from '@/types/root';
import { useFeedStateRestoration } from './useFeedStateRestoration';
import { FeedEntry } from '@/types/feed';

interface UsePublicationsSearchState {
data: PublicationSearchResponse | null;
Expand Down Expand Up @@ -103,101 +99,3 @@ export function useAddPublications(): UseAddPublicationsReturn {

return [{ isLoading, error }, addPublications];
}

interface UseAuthorPublicationsOptions {
authorId: ID;
initialData?: AuthorPublicationsResponse;
activeTab?: string; // Add activeTab for feed key generation
}

export function useAuthorPublications(options: UseAuthorPublicationsOptions) {
const { restoredState, initialEntries, restoredScrollPosition, lastClickedEntryId } =
useFeedStateRestoration({
activeTab: options.activeTab,
});

const initialHasRestoredEntries = restoredState !== null;

const [publications, setPublications] = useState<any[]>(options.initialData?.results || []);
const [isLoading, setIsLoading] = useState(!initialHasRestoredEntries && !options.initialData);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [currentResponse, setCurrentResponse] = useState<AuthorPublicationsResponse | null>(
options.initialData || null
);
const [error, setError] = useState<Error | null>(null);

const [restoredFeedEntries, setRestoredFeedEntries] = useState<FeedEntry[]>(initialEntries);
const [hasRestoredEntries, setHasRestoredEntries] = useState<boolean>(initialHasRestoredEntries);

const loadPublications = async () => {
setIsLoading(true);
setError(null);

try {
const response = await PublicationService.getAuthorPublications({
authorId: options.authorId,
});

setPublications(response.results);
setCurrentResponse(response);
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to load publications'));
console.error('Error loading publications:', err);
} finally {
setIsLoading(false);
}
};

useEffect(() => {
if (initialHasRestoredEntries && initialEntries.length > 0 && !hasRestoredEntries) {
setHasRestoredEntries(true);
setRestoredFeedEntries(initialEntries);
}
}, [initialHasRestoredEntries, initialEntries.length, hasRestoredEntries]);

useEffect(() => {
if (hasRestoredEntries && restoredFeedEntries.length > 0) {
return;
}

if (options.initialData) {
return;
}

loadPublications();
}, [options.authorId, options.initialData]);

const loadMore = async () => {
if (!currentResponse?.next || isLoading || isLoadingMore) {
return;
}

setIsLoadingMore(true);
setError(null);

try {
const nextPage = await PublicationService.loadMoreAuthorPublications(currentResponse);

setPublications((prev) => [...prev, ...nextPage.results]);
setCurrentResponse(nextPage);
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to load more publications'));
console.error('Error loading more publications:', err);
} finally {
setIsLoadingMore(false);
}
};

return {
publications,
isLoading,
error,
hasMore: !!currentResponse?.next,
loadMore,
refresh: loadPublications,
isLoadingMore,
restoredFeedEntries: hasRestoredEntries ? restoredFeedEntries : undefined,
restoredScrollPosition,
lastClickedEntryId,
};
}
2 changes: 1 addition & 1 deletion services/contribution.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ApiError } from './types';
import type { Contribution, ContributionListResponse } from '@/types/contribution';
import { ID } from '@/types/root';

export type ContributionType = 'CONVERSATION' | 'ARTICLE' | 'REVIEW' | 'BOUNTY' | 'ALL';
export type ContributionType = 'CONVERSATION' | 'REVIEW' | 'BOUNTY' | 'ALL';

export interface GetContributionsParams {
contribution_type?: ContributionType;
Expand Down
Loading
Loading