From 2c48f768770b1902f4fb8b6c3ef94b2be8918467 Mon Sep 17 00:00:00 2001 From: Kobe Attias Date: Sun, 11 Oct 2026 20:33:43 -0400 Subject: [PATCH] Retire the paper pages Seventh chunk of the dead-code plan: paper pages are no longer part of the product. - /paper and /paper/:path* redirect permanently to the homepage. The seven registered-report entries that map old paper URLs to /report stay ahead of the catch-all, so those links still land on the report. - app/paper goes (the slug and DOI redirect pages, the layout and the five tab pages), and with it the paper feed card, the journal and hashtag badges only it rendered, the paper header skeleton, the hot-score tooltip, the version-history tab and its display (versions were a paper feature), PaperService's metadata/abstract updaters and the OpenAlex creator, and the paper sitemap chunk. Two leftovers from main's earlier removals go in the same sweep: services/file.service.ts and components/ui/MainPageHeader. - buildWorkUrl no longer builds paper URLs: a paper (and any unknown content type) gets '#', which the mention renderers, the recently-visited rail and the activity cards already treat as "no link". The DOI argument only ever served paper URLs, so it goes. - The frontend stops rendering paper entries: FeedEntryItem returns nothing for a PAPER entry and the activity feed builds no work card for a paper event. Bounties and comments on papers still show, with no destination for the paper itself. - The search handlers keep routing recent and OpenAlex "paper" suggestions: proposals and posts go to their pages, papers go home. - The top bar, back-navigation list, share-title label, ESLint page glob and lint:work-pages script drop their paper entries. Co-Authored-By: Claude Fable 5.1 --- app/layouts/topbar/pageRoutes.tsx | 7 - app/paper/[id]/[slug]/bounties/page.tsx | 54 ----- app/paper/[id]/[slug]/conversation/page.tsx | 54 ----- app/paper/[id]/[slug]/history/page.tsx | 54 ----- app/paper/[id]/[slug]/layout.tsx | 67 ------ app/paper/[id]/[slug]/page.tsx | 55 ----- app/paper/[id]/[slug]/reviews/page.tsx | 54 ----- app/paper/[id]/page.tsx | 28 --- app/paper/layout.tsx | 3 - app/paper/loading.tsx | 12 - app/paper/page.tsx | 34 --- app/sitemap.ts | 11 +- components/Activity/lib/activityWork.utils.ts | 26 +-- components/Comment/lib/MentionExtension.tsx | 1 - components/Comment/lib/TipTapRenderer.tsx | 2 +- components/Feed/FeedEntryItem.tsx | 42 +--- components/Feed/FeedItemJournalBadge.tsx | 23 -- components/Feed/items/FeedItemPaper.tsx | 212 ------------------ .../RecentlyVisited/RecentlyVisitedCard.tsx | 1 - components/Search/Search.tsx | 17 +- components/Search/SearchModal.tsx | 17 +- components/modals/ShareModal.tsx | 1 - components/tooltips/HotScoreTooltip.tsx | 104 --------- components/ui/MainPageHeader.tsx | 25 --- components/ui/badges/HashtagBadge.tsx | 32 --- components/work/PaperHeaderSkeleton.tsx | 11 - components/work/WorkDocument.tsx | 12 - components/work/WorkHeader/WorkHeader.tsx | 4 +- components/work/WorkHeader/WorkTabContext.tsx | 1 - components/work/WorkHistoryDisplay.tsx | 104 --------- components/work/WorkTabs.tsx | 49 +--- eslint.config.mjs | 2 +- hooks/useSmartBack.ts | 2 +- next.config.js | 2 + package.json | 2 +- services/file.service.ts | 72 ------ services/paper.service.ts | 66 ------ types/search.ts | 2 - utils/url.ts | 21 +- 39 files changed, 42 insertions(+), 1244 deletions(-) delete mode 100644 app/paper/[id]/[slug]/bounties/page.tsx delete mode 100644 app/paper/[id]/[slug]/conversation/page.tsx delete mode 100644 app/paper/[id]/[slug]/history/page.tsx delete mode 100644 app/paper/[id]/[slug]/layout.tsx delete mode 100644 app/paper/[id]/[slug]/page.tsx delete mode 100644 app/paper/[id]/[slug]/reviews/page.tsx delete mode 100644 app/paper/[id]/page.tsx delete mode 100644 app/paper/layout.tsx delete mode 100644 app/paper/loading.tsx delete mode 100644 app/paper/page.tsx delete mode 100644 components/Feed/FeedItemJournalBadge.tsx delete mode 100644 components/Feed/items/FeedItemPaper.tsx delete mode 100644 components/tooltips/HotScoreTooltip.tsx delete mode 100644 components/ui/MainPageHeader.tsx delete mode 100644 components/ui/badges/HashtagBadge.tsx delete mode 100644 components/work/PaperHeaderSkeleton.tsx delete mode 100644 components/work/WorkHistoryDisplay.tsx delete mode 100644 services/file.service.ts diff --git a/app/layouts/topbar/pageRoutes.tsx b/app/layouts/topbar/pageRoutes.tsx index c3b1f1527..3a64d109c 100644 --- a/app/layouts/topbar/pageRoutes.tsx +++ b/app/layouts/topbar/pageRoutes.tsx @@ -155,13 +155,6 @@ const ROUTE_RULES: RouteRule[] = [ icon: , }), }, - { - match: (p) => p.startsWith('/paper/'), - getInfo: () => ({ - title: 'Paper', - icon: , - }), - }, { match: (p) => p.startsWith('/post/'), getInfo: () => ({ diff --git a/app/paper/[id]/[slug]/bounties/page.tsx b/app/paper/[id]/[slug]/bounties/page.tsx deleted file mode 100644 index 873f8bab4..000000000 --- a/app/paper/[id]/[slug]/bounties/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { PaperService } from '@/services/paper.service'; -import { MetadataService } from '@/services/metadata.service'; -import { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { WorkDocument } from '@/components/work/WorkDocument'; -import { SearchHistoryTracker } from '@/components/work/SearchHistoryTracker'; -import { WorkDocumentTracker } from '@/components/WorkDocumentTracker'; -import { getWorkMetadata } from '@/lib/metadata-helpers'; - -interface Props { - params: Promise<{ - id: string; - slug: string; - }>; -} - -async function getWork(id: string) { - if (!id.match(/^\d+$/)) { - notFound(); - } - - try { - return await PaperService.get(id); - } catch (error) { - notFound(); - } -} - -export async function generateMetadata({ params }: Props): Promise { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - return getWorkMetadata({ - work: work, - url: `/paper/${resolvedParams.id}/${resolvedParams.slug}/bounties`, - }); -} - -export default async function WorkBountiesPage({ params }: Props) { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - const metadata = await MetadataService.get(work.unifiedDocumentId?.toString() || ''); - - if (!work) { - notFound(); - } - - return ( - <> - - - - - ); -} diff --git a/app/paper/[id]/[slug]/conversation/page.tsx b/app/paper/[id]/[slug]/conversation/page.tsx deleted file mode 100644 index 20d0771fc..000000000 --- a/app/paper/[id]/[slug]/conversation/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { PaperService } from '@/services/paper.service'; -import { MetadataService } from '@/services/metadata.service'; -import { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { WorkDocument } from '@/components/work/WorkDocument'; -import { SearchHistoryTracker } from '@/components/work/SearchHistoryTracker'; -import { WorkDocumentTracker } from '@/components/WorkDocumentTracker'; -import { getWorkMetadata } from '@/lib/metadata-helpers'; - -interface Props { - params: Promise<{ - id: string; - slug: string; - }>; -} - -async function getWork(id: string) { - if (!id.match(/^\d+$/)) { - notFound(); - } - - try { - return await PaperService.get(id); - } catch (error) { - notFound(); - } -} - -export async function generateMetadata({ params }: Props): Promise { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - return getWorkMetadata({ - work: work, - url: `/paper/${resolvedParams.id}/${resolvedParams.slug}/conversation`, - }); -} - -export default async function WorkConversationPage({ params }: Props) { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - const metadata = await MetadataService.get(work.unifiedDocumentId?.toString() || ''); - - if (!work) { - notFound(); - } - - return ( - <> - - - - - ); -} diff --git a/app/paper/[id]/[slug]/history/page.tsx b/app/paper/[id]/[slug]/history/page.tsx deleted file mode 100644 index 0da1befa3..000000000 --- a/app/paper/[id]/[slug]/history/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { PaperService } from '@/services/paper.service'; -import { MetadataService } from '@/services/metadata.service'; -import { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { WorkDocument } from '@/components/work/WorkDocument'; -import { SearchHistoryTracker } from '@/components/work/SearchHistoryTracker'; -import { WorkDocumentTracker } from '@/components/WorkDocumentTracker'; -import { getWorkMetadata } from '@/lib/metadata-helpers'; - -interface Props { - params: Promise<{ - id: string; - slug: string; - }>; -} - -async function getWork(id: string) { - if (!id.match(/^\d+$/)) { - notFound(); - } - - try { - return await PaperService.get(id); - } catch (error) { - notFound(); - } -} - -export async function generateMetadata({ params }: Props): Promise { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - return getWorkMetadata({ - work: work, - url: `/paper/${resolvedParams.id}/${resolvedParams.slug}/history`, - }); -} - -export default async function WorkHistoryPage({ params }: Props) { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - const metadata = await MetadataService.get(work.unifiedDocumentId?.toString() || ''); - - if (!work) { - notFound(); - } - - return ( - <> - - - - - ); -} diff --git a/app/paper/[id]/[slug]/layout.tsx b/app/paper/[id]/[slug]/layout.tsx deleted file mode 100644 index 4a73c5bba..000000000 --- a/app/paper/[id]/[slug]/layout.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Suspense } from 'react'; -import { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { PaperService } from '@/services/paper.service'; -import { MetadataService } from '@/services/metadata.service'; -import { buildArticleMetadata } from '@/lib/metadata'; -import { stripHtml } from '@/utils/stringUtils'; -import { PageLayout } from '@/app/layouts/PageLayout'; -import { WorkHeader, WorkTabProvider } from '@/components/work/WorkHeader/index'; -import { WorkRightSidebar } from '@/components/work/WorkRightSidebar'; - -interface Props { - params: Promise<{ - id: string; - slug: string; - }>; - children: React.ReactNode; -} - -export async function generateMetadata({ params }: Props): Promise { - const { id, slug } = await params; - try { - const work = await PaperService.get(id); - const previewText = stripHtml(work.previewContent || '').substring(0, 155); - const description = work.abstract || previewText || 'Read this research paper on ResearchHub.'; - return buildArticleMetadata({ - title: work.title, - description, - url: `/paper/${id}/${slug}`, - image: work.image || work.figures?.[0]?.url, - publishedTime: work.publishedDate || work.createdDate, - modifiedTime: work.updatedDate, - authors: work.authors.map((a) => a.authorProfile.fullName), - section: work.topics?.[0]?.name, - tags: work.topics?.map((topic) => topic.name) ?? [], - }); - } catch { - return {}; - } -} - -export default async function PaperSlugLayout({ params, children }: Props) { - const { id } = await params; - - if (!id.match(/^\d+$/)) { - notFound(); - } - - let work; - try { - work = await PaperService.get(id); - } catch { - notFound(); - } - const metadata = await MetadataService.get(work.unifiedDocumentId?.toString() || ''); - - return ( - - } - rightSidebar={} - > - {children} - - - ); -} diff --git a/app/paper/[id]/[slug]/page.tsx b/app/paper/[id]/[slug]/page.tsx deleted file mode 100644 index 8bcc4844a..000000000 --- a/app/paper/[id]/[slug]/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { PaperService } from '@/services/paper.service'; -import { MetadataService } from '@/services/metadata.service'; -import { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { WorkDocument } from '@/components/work/WorkDocument'; -import { SearchHistoryTracker } from '@/components/work/SearchHistoryTracker'; -import { WorkDocumentTracker } from '@/components/WorkDocumentTracker'; -import { getWorkMetadata } from '@/lib/metadata-helpers'; - -interface Props { - params: Promise<{ - id: string; - slug: string; - }>; -} - -async function getWork(id: string) { - if (!id.match(/^\d+$/)) { - notFound(); - } - - try { - return await PaperService.get(id); - } catch (error) { - notFound(); - } -} - -export async function generateMetadata({ params }: Props): Promise { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - - return getWorkMetadata({ - work: work, - url: `/paper/${resolvedParams.id}/${resolvedParams.slug}`, - }); -} - -export default async function WorkPage({ params }: Props) { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - const metadata = await MetadataService.get(work.unifiedDocumentId?.toString() || ''); - - if (!work) { - notFound(); - } - - return ( - <> - - - - - ); -} diff --git a/app/paper/[id]/[slug]/reviews/page.tsx b/app/paper/[id]/[slug]/reviews/page.tsx deleted file mode 100644 index 395ffbe95..000000000 --- a/app/paper/[id]/[slug]/reviews/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { PaperService } from '@/services/paper.service'; -import { MetadataService } from '@/services/metadata.service'; -import { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { WorkDocument } from '@/components/work/WorkDocument'; -import { SearchHistoryTracker } from '@/components/work/SearchHistoryTracker'; -import { WorkDocumentTracker } from '@/components/WorkDocumentTracker'; -import { getWorkMetadata } from '@/lib/metadata-helpers'; - -interface Props { - params: Promise<{ - id: string; - slug: string; - }>; -} - -async function getWork(id: string) { - if (!id.match(/^\d+$/)) { - notFound(); - } - - try { - return await PaperService.get(id); - } catch (error) { - notFound(); - } -} - -export async function generateMetadata({ params }: Props): Promise { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - return getWorkMetadata({ - work: work, - url: `/paper/${resolvedParams.id}/${resolvedParams.slug}/reviews`, - }); -} - -export default async function WorkReviewsPage({ params }: Props) { - const resolvedParams = await params; - const work = await getWork(resolvedParams.id); - const metadata = await MetadataService.get(work.unifiedDocumentId?.toString() || ''); - - if (!work) { - notFound(); - } - - return ( - <> - - - - - ); -} diff --git a/app/paper/[id]/page.tsx b/app/paper/[id]/page.tsx deleted file mode 100644 index 2aba55919..000000000 --- a/app/paper/[id]/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { notFound } from 'next/navigation'; -import { PaperService } from '@/services/paper.service'; -import { handleMissingSlugRedirect } from '@/utils/navigation'; - -interface Props { - params: Promise<{ - id: string; - }>; -} - -export default async function PaperRedirectPage({ params }: Props) { - const resolvedParams = await params; - const id = resolvedParams.id; - - if (!id.match(/^\d+$/)) { - notFound(); - } - - let paper; - try { - paper = await PaperService.get(id); - } catch (error) { - notFound(); - } - - // Redirect to the full URL with slug (outside try-catch) - handleMissingSlugRedirect(paper, id, 'paper'); -} diff --git a/app/paper/layout.tsx b/app/paper/layout.tsx deleted file mode 100644 index ce518aaf2..000000000 --- a/app/paper/layout.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function WorkLayout(props: { children: React.ReactNode }) { - return props.children; -} diff --git a/app/paper/loading.tsx b/app/paper/loading.tsx deleted file mode 100644 index 849d75755..000000000 --- a/app/paper/loading.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PageLayout } from '@/app/layouts/PageLayout'; -import { WorkSkeleton } from '@/components/work/WorkSkeleton'; -import { PaperHeaderSkeleton } from '@/components/work/PaperHeaderSkeleton'; -import { WorkRightSidebarSkeleton } from '@/components/work/WorkRightSidebarSkeleton'; - -export default function PaperLoading() { - return ( - } rightSidebar={}> - - - ); -} diff --git a/app/paper/page.tsx b/app/paper/page.tsx deleted file mode 100644 index ab48fb34f..000000000 --- a/app/paper/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { PaperService } from '@/services/paper.service'; -import { redirect } from 'next/navigation'; -import { buildWorkUrl, generateSlug } from '@/utils/url'; - -type Props = { - params: Promise>; - searchParams: Promise<{ [key: string]: string | string[] | undefined }>; -}; - -export default async function WorkPage({ params, searchParams }: Props) { - const searchParamsData = await searchParams; - const doi = searchParamsData.doi; - if (!doi || typeof doi !== 'string') { - redirect('/404'); - } - - try { - // This will show loading state in @loading slot while fetching - const work = await PaperService.get(doi); - redirect( - buildWorkUrl({ - id: work.id, - contentType: 'paper', - slug: work.slug || generateSlug(work.title), - }) - ); - } catch (error: any) { - if (!error.digest?.startsWith('NEXT_REDIRECT')) { - console.error('Error importing work:', error); - redirect('/404'); - } - throw error; // Re-throw Next.js redirects - } -} diff --git a/app/sitemap.ts b/app/sitemap.ts index 131c47995..1ccb46c44 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -79,7 +79,7 @@ const STATIC_ROUTES: MetadataRoute.Sitemap = [ ]; export async function generateSitemaps() { - return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]; + return [{ id: 0 }, { id: 1 }, { id: 2 }]; } export default async function sitemap({ @@ -90,18 +90,11 @@ export default async function sitemap({ const sitemapId = Number(await id); if (sitemapId === 0) return STATIC_ROUTES; if (sitemapId === 1) - return buildSlugSitemap( - '/api/feed/?content_type=PAPER&page_size=1000', - '/paper/', - (item) => item.content_object - ); - - if (sitemapId === 2) return buildSlugSitemap( '/api/researchhubpost/?document_type=PREREGISTRATION&page_size=1000', '/proposal/' ); - if (sitemapId === 3) + if (sitemapId === 2) return buildSlugSitemap('/api/researchhubpost/?document_type=GRANT&page_size=100', '/grant/'); return []; diff --git a/components/Activity/lib/activityWork.utils.ts b/components/Activity/lib/activityWork.utils.ts index 02aeece60..bf23b2592 100644 --- a/components/Activity/lib/activityWork.utils.ts +++ b/components/Activity/lib/activityWork.utils.ts @@ -15,7 +15,6 @@ import type { FeedBountyContent, FeedEntry, FeedGrantContent, - FeedPaperContent, FeedPostContent, } from '@/types/feed'; import type { Bounty } from '@/types/bounty'; @@ -358,29 +357,8 @@ function grantSummaryFromFeedGrant(content: FeedGrantContent): WorkGrantSummary function getWorkFromContent(entry: FeedEntry): ActivityWork | null { const bounty = getActivityBounty(entry); - if (entry.contentType === 'PAPER') { - const paper = entry.content as FeedPaperContent; - if (!paper.title) return null; - const documentType: ContentType = 'paper'; - const tab = resolveWorkTab(entry, documentType); - return { - id: paper.id, - slug: paper.slug, - title: paper.title, - href: buildWorkUrl({ - id: paper.id, - slug: paper.slug, - contentType: documentType, - tab, - }), - imageUrl: paper.previewImage || paper.previewThumbnail, - documentType, - unifiedDocumentId: toOptionalNumber(paper.unifiedDocumentId), - bounty, - authors: paper.authors, - tab, - }; - } + // Paper pages are retired, so a paper event has no work card to show. + if (entry.contentType === 'PAPER') return null; if (entry.contentType === 'GRANT') { const grantContent = entry.content as FeedGrantContent; diff --git a/components/Comment/lib/MentionExtension.tsx b/components/Comment/lib/MentionExtension.tsx index 9cf6a32eb..5a34a909d 100644 --- a/components/Comment/lib/MentionExtension.tsx +++ b/components/Comment/lib/MentionExtension.tsx @@ -193,7 +193,6 @@ export const MentionExtension = Mention.extend({ const url = buildWorkUrl({ id: node.attrs.id, contentType: 'paper', - doi: node.attrs.doi, }); // If we got a fallback URL, render as a span instead of a link diff --git a/components/Comment/lib/TipTapRenderer.tsx b/components/Comment/lib/TipTapRenderer.tsx index dce448329..06e50c7c8 100644 --- a/components/Comment/lib/TipTapRenderer.tsx +++ b/components/Comment/lib/TipTapRenderer.tsx @@ -622,7 +622,7 @@ const RenderNode: React.FC = ({ // For papers if (entityType === 'paper') { - const href = buildWorkUrl({ id, doi: node.attrs?.doi, contentType: 'paper' }); + const href = buildWorkUrl({ id, contentType: 'paper' }); if (!href || href === '#') { return {label}; } diff --git a/components/Feed/FeedEntryItem.tsx b/components/Feed/FeedEntryItem.tsx index 668599472..026c92d60 100644 --- a/components/Feed/FeedEntryItem.tsx +++ b/components/Feed/FeedEntryItem.tsx @@ -2,15 +2,8 @@ import { FC, useCallback } from 'react'; import { useInView } from 'react-intersection-observer'; -import { - FeedCommentContent, - FeedEntry, - FeedPostContent, - FeedPaperContent, - FeedGrantContent, -} from '@/types/feed'; +import { FeedCommentContent, FeedEntry, FeedPostContent, FeedGrantContent } from '@/types/feed'; import { FeedItemFundraise } from './items/DeprecatedFeedItemFundraise'; -import { FeedItemPaper } from './items/FeedItemPaper'; import { FeedItemComment } from './items/FeedItemComment'; import { FeedItemPost } from './items/FeedItemPost'; import { FeedItemGrant } from './items/FeedItemGrant'; @@ -97,6 +90,11 @@ export const FeedEntryItem: FC = ({ return null; } + // Paper pages are retired, so paper entries have nowhere to go. + if (entry.contentType === 'PAPER') { + return null; + } + // Apply appropriate spacing based on position const spacingClass = index !== 0 ? 'mt-8' : ''; @@ -120,15 +118,6 @@ export const FeedEntryItem: FC = ({ contentType: 'preregistration', }); } - case 'PAPER': { - const paperContent = entry.content as FeedPaperContent; - return buildWorkUrl({ - id: paperContent.id, - slug: paperContent.slug, - contentType: 'paper', - }); - } - case 'BOUNTY': if (entry.relatedWork) { let contentType = entry.relatedWork.contentType; @@ -202,9 +191,7 @@ export const FeedEntryItem: FC = ({ // When showBountyInfo is set and the entry has open bounties, render as a uniform bounty card if ( shouldRenderAsBountyCard && - (entry.contentType === 'POST' || - entry.contentType === 'PAPER' || - entry.contentType === 'PREREGISTRATION') + (entry.contentType === 'POST' || entry.contentType === 'PREREGISTRATION') ) { content = ( = ({ ); break; - case 'PAPER': - content = ( - - ); - break; - case 'BOUNTY': { if (shouldRenderBountyAsComment) { content = ( diff --git a/components/Feed/FeedItemJournalBadge.tsx b/components/Feed/FeedItemJournalBadge.tsx deleted file mode 100644 index 3df98688e..000000000 --- a/components/Feed/FeedItemJournalBadge.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client'; - -import { FC } from 'react'; -import { HashtagBadge } from '@/components/ui/badges/HashtagBadge'; -import { Journal } from '@/types/journal'; -import { getSourceLogo } from '@/utils/preprintUtil'; - -interface FeedItemJournalBadgeProps { - journal?: Journal; -} - -/** - * The hashtag-style journal badge (e.g. #biorxiv) on a paper feed item. Only - * the ResearchHub Journal has a page to link to. - */ -export const FeedItemJournalBadge: FC = ({ journal }) => { - if (!journal?.name || !journal.slug) return null; - const logo = getSourceLogo(journal.slug); - if (!logo) return null; - return ( - - ); -}; diff --git a/components/Feed/items/FeedItemPaper.tsx b/components/Feed/items/FeedItemPaper.tsx deleted file mode 100644 index 87ff0f133..000000000 --- a/components/Feed/items/FeedItemPaper.tsx +++ /dev/null @@ -1,212 +0,0 @@ -'use client'; - -import { FC } from 'react'; -import Image from 'next/image'; -import { useSearchParams } from 'next/navigation'; -import { FeedPaperContent, FeedEntry } from '@/types/feed'; -import { - BaseFeedItem, - TitleSection, - ImageSection, - MetadataSection, - FeedItemTopSection, -} from '@/components/Feed/BaseFeedItem'; -import { FeedItemAbstractSection } from '@/components/Feed/FeedItemAbstractSection'; -import { FeedItemJournalBadge } from '@/components/Feed/FeedItemJournalBadge'; -import { AuthorList } from '@/components/ui/AuthorList'; -import { Tooltip } from '@/components/ui/Tooltip'; -import { PopularityScoreTooltip } from '@/components/tooltips/HotScoreTooltip'; -import { PeerReviewTooltip } from '@/components/tooltips/PeerReviewTooltip'; -import { Star } from 'lucide-react'; -import { formatTimestamp } from '@/utils/date'; -import { buildWorkUrl } from '@/utils/url'; - -interface FeedItemPaperProps { - entry: FeedEntry; - href?: string; - showTooltips?: boolean; - showActions?: boolean; - maxLength?: number; - onFeedItemClick?: () => void; - onAbstractExpanded?: () => void; - showBountyInfo?: boolean; - abstractCollapsedByDefault?: boolean; -} - -/** - * Component for rendering a paper feed item using BaseFeedItem - */ -export const FeedItemPaper: FC = ({ - entry, - href, - showTooltips = true, - showActions = true, - maxLength, - onFeedItemClick, - onAbstractExpanded, - showBountyInfo, - abstractCollapsedByDefault, -}) => { - const searchParams = useSearchParams(); - const isDebugMode = searchParams.has('debug'); - - // Extract the paper from the entry's content - const paper = entry.content as FeedPaperContent; - - // Use provided href or create default paper page URL - const paperPageUrl = - href || - buildWorkUrl({ - id: paper.id, - slug: paper.slug, - contentType: 'paper', - }); - - // Construct the dynamic action text - const journalName = paper.journal?.name; - const actionText = journalName ? `published in ${journalName}` : 'published in a journal'; - - // Only show journal badge for specific preprint servers - const ALLOWED_JOURNALS = ['biorxiv', 'arxiv', 'medrxiv', 'chemrxiv']; - const journalSlugLower = paper.journal?.slug?.toLowerCase() || ''; - const shouldShowJournal = ALLOWED_JOURNALS.some((j) => journalSlugLower.includes(j)); - const filteredJournal = shouldShowJournal ? paper.journal : undefined; - - const thumbnailUrl = paper.previewThumbnail || paper.journal?.imageUrl; - const isPdfPreview = thumbnailUrl?.includes('preview'); - - const reviewScore = entry.metrics?.reviewScore; - const hasReviewScore = reviewScore !== undefined && reviewScore > 0; - - return ( - - ) : undefined - } - badges={} - > - {/* Top section with mobile image */} - - ) - } - rightContent={ - isDebugMode && entry.hotScoreV2 !== undefined && entry.hotScoreV2 > 0 ? ( - - } - width="w-72" - position="bottom" - > -
- Popularity Score - {Math.round(entry.hotScoreV2)} -
-
- ) : null - } - leftContent={null} - /> - - - - {/* Authors and Date */} - -
- {paper.authors.length > 0 && ( - ({ - name: author.fullName, - verified: author.user?.isVerified, - authorUrl: author.id === 0 ? undefined : author.profileUrl, - }))} - size="base" - className="text-gray-500 font-normal text-sm" - delimiter="," - delimiterClassName="ml-0" - showAbbreviatedInMobile={true} - hideExpandButton={true} - /> - )} - {(entry.timestamp || paper.createdDate) && ( - <> - {paper.authors.length > 0 && •} - - {formatTimestamp(entry.timestamp || paper.createdDate, false)} - - - )} - {hasReviewScore && ( - <> - • - - } - position="top" - width="w-[320px]" - > - - - {reviewScore.toFixed(1)} - - - - )} -
-
- - -
- ); -}; diff --git a/components/RecentlyVisited/RecentlyVisitedCard.tsx b/components/RecentlyVisited/RecentlyVisitedCard.tsx index c267cbc30..703b8ab13 100644 --- a/components/RecentlyVisited/RecentlyVisitedCard.tsx +++ b/components/RecentlyVisited/RecentlyVisitedCard.tsx @@ -53,7 +53,6 @@ function toRecentPage(visit: VisitRecord): RecentPage | null { const href = buildWorkUrl({ id: visit.id, contentType, - doi: 'doi' in visit ? visit.doi : undefined, slug: visit.slug, }); if (!href || href === '#') return null; diff --git a/components/Search/Search.tsx b/components/Search/Search.tsx index 51f7e4c39..7c16d375c 100644 --- a/components/Search/Search.tsx +++ b/components/Search/Search.tsx @@ -64,20 +64,13 @@ export function Search({ // Default behavior if (suggestion.entityType === 'paper') { - // Check if this is a funding request - if (suggestion.contentType === 'preregistration') { + if (suggestion.contentType === 'preregistration' && suggestion.id) { router.push(`/proposal/${suggestion.id}/${suggestion.slug || ''}`); - } else if (suggestion.isRecent) { - router.push(`/paper/${suggestion.id}/${suggestion.slug}`); + } else if (suggestion.contentType && suggestion.contentType !== 'paper' && suggestion.id) { + router.push(`/post/${suggestion.id}`); } else { - if (suggestion.id) { - router.push(`/paper/${suggestion.id}/${suggestion.slug || ''}`); - } else if ('doi' in suggestion && suggestion.doi) { - router.push(`/paper?doi=${encodeURIComponent(suggestion.doi)}`); - } else { - // Fallback if no identifiers are available - router.push('/'); - } + // Paper pages are retired. + router.push('/'); } } else if (suggestion.entityType === 'user' || suggestion.entityType === 'author') { navigateToAuthorProfile(suggestion.authorProfile.id); diff --git a/components/Search/SearchModal.tsx b/components/Search/SearchModal.tsx index be8b1fa3e..cd1681f99 100644 --- a/components/Search/SearchModal.tsx +++ b/components/Search/SearchModal.tsx @@ -48,16 +48,13 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) { // Navigate based on suggestion type if (suggestion.entityType === 'paper') { - if (suggestion.isRecent && suggestion.contentType && suggestion.contentType !== 'paper') { - if (suggestion.id) { - router.push(`/post/${suggestion.id}`); - } else { - router.push('/'); - } - } else if (suggestion.id) { - router.push(`/paper/${suggestion.id}/${suggestion.slug || ''}`); - } else if ('doi' in suggestion && suggestion.doi) { - router.push(`/paper?doi=${encodeURIComponent(suggestion.doi)}`); + if (suggestion.contentType === 'preregistration' && suggestion.id) { + router.push(`/proposal/${suggestion.id}/${suggestion.slug || ''}`); + } else if (suggestion.contentType && suggestion.contentType !== 'paper' && suggestion.id) { + router.push(`/post/${suggestion.id}`); + } else { + // Paper pages are retired. + router.push('/'); } } else if (suggestion.entityType === 'user' || suggestion.entityType === 'author') { navigateToAuthorProfile(suggestion.authorProfile.id); diff --git a/components/modals/ShareModal.tsx b/components/modals/ShareModal.tsx index fa40c9ab7..fb13fdba6 100644 --- a/components/modals/ShareModal.tsx +++ b/components/modals/ShareModal.tsx @@ -65,7 +65,6 @@ export const SHARE_CONFIGS: Record = { title: (docTitle, url) => { const getShareType = (url: string): string => { if (url.includes('/post/')) return 'Preprint'; - if (url.includes('/paper/')) return 'Paper'; if (url.includes('/proposal/')) return 'Proposal'; if (url.includes('/grant/')) return 'Request for Proposal'; if (url.includes('/report/')) return 'Registered Report'; diff --git a/components/tooltips/HotScoreTooltip.tsx b/components/tooltips/HotScoreTooltip.tsx deleted file mode 100644 index 62765d94f..000000000 --- a/components/tooltips/HotScoreTooltip.tsx +++ /dev/null @@ -1,104 +0,0 @@ -'use client'; - -import React from 'react'; -import { Clock, ArrowUp, Coins, Star, MessageCircle, Eye } from 'lucide-react'; -import { HotScoreBreakdown } from '@/types/feed'; -import Icon from '@/components/ui/icons/Icon'; - -// Custom Github icon component -const GithubIcon = ({ className }: { className?: string }) => ( - - - -); - -// Custom Bluesky icon component -const BlueskyIcon = ({ className }: { className?: string }) => ( - - - -); - -// Wrapper for the custom Icon component to match the expected interface -const EarnIcon = ({ className }: { className?: string }) => ( - -); - -interface PopularityScoreTooltipProps { - score: number; - breakdown?: HotScoreBreakdown; -} - -// Signal configuration with display names and icons -// Keys must match the API response: recency, upvote, tip, peer_review, comment, bounty, github, bluesky, page_views -const SIGNAL_CONFIG: { - key: string; - label: string; - icon: React.ComponentType<{ className?: string }>; -}[] = [ - { key: 'recency', label: 'Recency', icon: Clock }, - { key: 'page_views', label: 'Page Views', icon: Eye }, - { key: 'upvote', label: 'Upvotes', icon: ArrowUp }, - { key: 'tip', label: 'Tips', icon: Coins }, - { key: 'peer_review', label: 'Peer Reviews', icon: Star }, - { key: 'comment', label: 'Comments', icon: MessageCircle }, - { key: 'bounty', label: 'Bounty', icon: EarnIcon }, - { key: 'github', label: 'Github', icon: GithubIcon }, - { key: 'bluesky', label: 'Bluesky', icon: BlueskyIcon }, -]; - -const formatNumber = (num: number | null | undefined) => { - if (num === null || num === undefined) return '0'; - if (num === 0) return '0'; - if (num >= 1000) { - return `${(num / 1000).toFixed(1)}k`; - } - // For small decimals (like recency), show 2 decimal places - if (num > 0 && num < 1) { - return num.toFixed(2); - } - return Math.round(num).toString(); -}; - -export function PopularityScoreTooltip({ score, breakdown }: PopularityScoreTooltipProps) { - const signals = breakdown?.signals || {}; - - const handleClick = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - }; - - return ( -
- {/* Popularity Score - Centered Large Number */} -
-

Popularity Score

- {Math.round(score)} -
- - {/* Explanatory text */} -

- The popularity score is calculated based on multiple engagement signals including recency, - views, votes, tips, and social activity. -

- - {/* Signals List */} -
- {SIGNAL_CONFIG.map(({ key, label, icon: SignalIcon }) => { - const signal = signals[key]; - const rawValue = signal?.raw ?? 0; - - return ( -
-
- - {label} -
- {formatNumber(rawValue)} -
- ); - })} -
-
- ); -} diff --git a/components/ui/MainPageHeader.tsx b/components/ui/MainPageHeader.tsx deleted file mode 100644 index 5b72f2a87..000000000 --- a/components/ui/MainPageHeader.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { ReactNode } from 'react'; - -interface MainPageHeaderProps { - title: string; - subtitle: string; - icon: ReactNode; - showTitle?: boolean; -} - -export const MainPageHeader: React.FC = ({ - title, - subtitle, - icon, - showTitle = true, -}) => { - return ( - <> - {!showTitle &&

{title}

} -
- {showTitle &&

{title}

} -

{subtitle}

-
- - ); -}; diff --git a/components/ui/badges/HashtagBadge.tsx b/components/ui/badges/HashtagBadge.tsx deleted file mode 100644 index 968f87f83..000000000 --- a/components/ui/badges/HashtagBadge.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FC } from 'react'; -import Link from 'next/link'; - -interface HashtagBadgeProps { - label: string; - /** Where the badge goes; without one it is plain text. */ - href?: string; - className?: string; -} - -const STYLE = { fontSize: '13px' }; - -export const HashtagBadge: FC = ({ label, href, className = '' }) => { - const baseClass = `flex-shrink-0 text-gray-500 ${className}`; - if (!href) { - return ( - - #{label} - - ); - } - return ( - e.stopPropagation()} - > - #{label} - - ); -}; diff --git a/components/work/PaperHeaderSkeleton.tsx b/components/work/PaperHeaderSkeleton.tsx deleted file mode 100644 index 44b419158..000000000 --- a/components/work/PaperHeaderSkeleton.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client'; - -import { useParams } from 'next/navigation'; -import { WorkHeaderSkeleton } from '@/components/work/WorkSkeleton'; - -export function PaperHeaderSkeleton() { - const params = useParams(); - const slug = typeof params?.slug === 'string' ? params.slug : undefined; - - return ; -} diff --git a/components/work/WorkDocument.tsx b/components/work/WorkDocument.tsx index 2d950c7da..9f5d3e7d8 100644 --- a/components/work/WorkDocument.tsx +++ b/components/work/WorkDocument.tsx @@ -8,7 +8,6 @@ import { WorkMetadata } from '@/services/metadata.service'; import { DocumentViewer } from './DocumentViewer'; import { CommentFeed } from '@/components/Comment/CommentFeed'; import { TabType } from './WorkTabs'; -import { WorkHistoryDisplay } from './WorkHistoryDisplay'; import { Button } from '@/components/ui/Button'; import { ReviewStatusBanner } from '@/components/Bounty/ReviewStatusBanner'; @@ -135,17 +134,6 @@ export const WorkDocument = ({ work, metadata }: WorkDocumentProps) => { /> ); - case 'history': - return ( - <> -

Version History

- - - ); default: return null; } diff --git a/components/work/WorkHeader/WorkHeader.tsx b/components/work/WorkHeader/WorkHeader.tsx index 9995fc5a0..527c1a9d8 100644 --- a/components/work/WorkHeader/WorkHeader.tsx +++ b/components/work/WorkHeader/WorkHeader.tsx @@ -40,7 +40,7 @@ import { useWorkTab } from './WorkTabContext'; interface WorkHeaderProps { work: Work; metadata: WorkMetadata; - contentType?: 'paper' | 'post' | 'fund'; + contentType?: 'post' | 'fund'; updatesCount?: number; className?: string; eyebrow?: ReactNode; @@ -66,7 +66,7 @@ interface WorkHeaderProps { export function WorkHeader({ work, metadata, - contentType = 'paper', + contentType = 'post', updatesCount, className, eyebrow: eyebrowOverride, diff --git a/components/work/WorkHeader/WorkTabContext.tsx b/components/work/WorkHeader/WorkTabContext.tsx index af969c4b2..46e94c092 100644 --- a/components/work/WorkHeader/WorkTabContext.tsx +++ b/components/work/WorkHeader/WorkTabContext.tsx @@ -31,7 +31,6 @@ function getTabFromPath(path: string): TabType { if (path.includes('/applications')) return 'applications'; if (path.includes('/reviews')) return 'reviews'; if (path.includes('/bounties')) return 'bounties'; - if (path.includes('/history')) return 'history'; return 'paper'; } diff --git a/components/work/WorkHistoryDisplay.tsx b/components/work/WorkHistoryDisplay.tsx deleted file mode 100644 index 2f1aa2bde..000000000 --- a/components/work/WorkHistoryDisplay.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import Link from 'next/link'; -import { cn } from '@/utils/styles'; -import { ContentTypeBadge } from '@/components/ui/ContentTypeBadge'; -import { DocumentVersion } from '@/types/work'; -import { VersionOfRecordBadge } from '@/components/ui/VersionOfRecordBadge'; - -interface WorkHistoryDisplayProps { - versions: DocumentVersion[]; - currentPaperId: number; // ID of the work currently being viewed - slug: string; // Slug for navigation -} - -export const WorkHistoryDisplay = ({ versions, currentPaperId, slug }: WorkHistoryDisplayProps) => { - if (!versions || versions.length === 0) { - return

No version history available for this document.

; - } - - // Sort versions descending by published date, then by version number if equal - const sortedVersions = [...versions].sort((a, b) => { - const dateDiff = new Date(b.publishedDate).getTime() - new Date(a.publishedDate).getTime(); - if (dateDiff !== 0) return dateDiff; - // Compare version numbers - return b.version - a.version; - }); - - return ( -
-
- {sortedVersions.map((version) => { - const isViewingThisCard = version.paperId === currentPaperId; - const isLatest = version.isLatest; - const publishedDate = new Date(version.publishedDate); - - // Generate the URL for this version - const versionUrl = `/paper/${version.paperId}/${slug}`; - - return ( -
- -
-
- {/* Left */} -
-
-

- Version {version.version} -

- {version.isVersionOfRecord && } - {version.publicationStatus !== 'PUBLISHED' && ( - - )} -
- {version.message && ( -

- {version.message} -

- )} -
- - Published{' '} - {publishedDate.toLocaleDateString(undefined, { - year: 'numeric', - month: 'long', - day: 'numeric', - })} - -
-
-
-
- -
- ); - })} -
-
- ); -}; diff --git a/components/work/WorkTabs.tsx b/components/work/WorkTabs.tsx index 613354d2e..752ccc274 100644 --- a/components/work/WorkTabs.tsx +++ b/components/work/WorkTabs.tsx @@ -1,6 +1,6 @@ 'use client'; -import { FileText, Star, MessageCircle, History, Bell, MessageCircleQuestion } from 'lucide-react'; +import { FileText, Star, MessageCircle, Bell, MessageCircleQuestion } from 'lucide-react'; import { Work } from '@/types/work'; import { WorkMetadata } from '@/services/metadata.service'; import { useState, useEffect, useMemo } from 'react'; @@ -25,14 +25,13 @@ export type TabType = | 'reviews' | 'bounties' | 'conversation' - | 'history' | 'applications'; interface WorkTabsProps { work: Work; metadata: WorkMetadata; defaultTab?: TabType; - contentType?: 'paper' | 'post' | 'fund'; + contentType?: 'post' | 'fund'; onTabChange: (tab: TabType) => void; updatesCount?: number; } @@ -40,7 +39,7 @@ interface WorkTabsProps { export const WorkTabs = ({ work, metadata, - contentType = 'paper', + contentType = 'post', onTabChange, updatesCount = 0, }: WorkTabsProps) => { @@ -76,7 +75,6 @@ export const WorkTabs = ({ if (path.includes('/applications')) return 'applications'; if (path.includes('/reviews')) return 'reviews'; if (path.includes('/bounties')) return 'bounties'; - if (path.includes('/history') && hasResearchHubJournalVersions) return 'history'; return 'paper'; }; @@ -108,13 +106,11 @@ export const WorkTabs = ({ if (typeof window !== 'undefined') { const baseUrl = - contentType === 'paper' - ? `/paper/${work.id}/${work.slug}` - : contentType === 'fund' - ? `/proposal/${work.id}/${work.slug}` - : work.postType === 'QUESTION' - ? `/question/${work.id}/${work.slug}` - : `/post/${work.id}/${work.slug}`; + contentType === 'fund' + ? `/proposal/${work.id}/${work.slug}` + : work.postType === 'QUESTION' + ? `/question/${work.id}/${work.slug}` + : `/post/${work.id}/${work.slug}`; const tabUrlMap: Partial> = { updates: `${baseUrl}/updates`, @@ -122,7 +118,6 @@ export const WorkTabs = ({ reviews: `${baseUrl}/reviews`, applications: `${baseUrl}/applications`, bounties: `${baseUrl}/bounties`, - history: `${baseUrl}/history`, }; const newUrl = tabUrlMap[tab] || baseUrl; @@ -140,7 +135,6 @@ export const WorkTabs = ({ // Get the appropriate label for the main content tab const getMainTabLabel = () => { - if (contentType === 'paper') return 'Paper'; if (contentType === 'fund') return 'Project'; if (work.postType === 'QUESTION') return 'Question'; return 'Post'; @@ -272,32 +266,7 @@ export const WorkTabs = ({ }, ]; - const tabs = useMemo(() => { - if (hasResearchHubJournalVersions) { - return [ - ...baseTabs, - { - id: 'history', - label: ( -
- - History - - {work.versions?.length || 0} - -
- ), - }, - ]; - } - return baseTabs; - }, [baseTabs, hasResearchHubJournalVersions, activeTab, work.versions?.length]); + const tabs = baseTabs; return ( { const router = useRouter(); diff --git a/next.config.js b/next.config.js index 440895ad4..888123b03 100644 --- a/next.config.js +++ b/next.config.js @@ -166,6 +166,8 @@ const nextConfig = { '/report/32650/identifying-the-point-of-no-return-integrating-single-cell-transcriptomics-and-proteomics-to-map-irreversible-fibroblast-commitment-in-idiopathic-pulmonary-fibrosis-stage-1-registered-report', permanent: true, }, + // Paper pages are retired; anything else under /paper lands on the homepage. + ...['/paper', '/paper/:path*'].map((source) => ({ source, destination: '/', permanent: true })), ], // app/sitemap.ts uses generateSitemaps, which serves only /sitemap/.xml, // and Next still reserves app/sitemap.xml for it, so the index lives at diff --git a/package.json b/package.json index 30a540fa9..5e0b68986 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "start": "next start", "lint": "eslint .", "lint:fix": "eslint . --fix", - "lint:work-pages": "eslint \"app/{paper,post,proposal,question,report}/[[]id[]]/[[]slug[]]/**/page.tsx\"", + "lint:work-pages": "eslint \"app/{post,proposal,question,report}/[[]id[]]/[[]slug[]]/**/page.tsx\"", "format": "prettier --write --ignore-path .gitignore \"./**/*.{js,jsx,ts,tsx,json,css}\"", "type-check": "tsc --noEmit", "type-check:watch": "tsc --noEmit --watch", diff --git a/services/file.service.ts b/services/file.service.ts deleted file mode 100644 index 3366fa579..000000000 --- a/services/file.service.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { ApiClient } from './client'; - -export interface UploadFileResult { - objectKey: string; - absoluteUrl: string; - fileName: string; -} - -export class FileService { - private static readonly BASE_PATH = '/'; - - /** - * Uploads a file to the server - * - * @param file The file to upload - * @returns A promise that resolves to the upload result - */ - static async uploadFile(file: File): Promise { - try { - const formData = new FormData(); - formData.append('filename', file.name); - - const url = process.env.NEXT_PUBLIC_API_URL + this.BASE_PATH + 'paper/upload/'; - - // Get the authorization header using ApiClient helper - const headers = await ApiClient['getHeaders']('POST'); - - // Remove Content-Type header so browser can set correct multipart boundary - delete headers['Content-Type']; - - // Get the presigned URL - const response = await fetch(url, { - method: 'POST', - headers, - body: formData, - }); - - if (!response.ok) { - throw new Error(`Failed to get presigned URL: ${response.statusText}`); - } - - const data = await response.json(); - const { presigned_url: presignedUrl, object_key: objectKey, object_url: objectUrl } = data; - - if (!presignedUrl || !objectKey) { - throw new Error('Invalid response from server'); - } - - // Upload the file to S3 using the presigned URL - const uploadResponse = await fetch(presignedUrl, { - method: 'PUT', - body: file, - headers: { - 'Content-Type': file.type || 'application/octet-stream', - }, - }); - - if (!uploadResponse.ok) { - throw new Error(`Failed to upload file: ${uploadResponse.statusText}`); - } - - return { - objectKey: objectKey, - absoluteUrl: objectUrl, - fileName: file.name, - }; - } catch (error) { - console.error('File upload failed:', error); - throw error; - } - } -} diff --git a/services/paper.service.ts b/services/paper.service.ts index 3156d9b30..744704de2 100644 --- a/services/paper.service.ts +++ b/services/paper.service.ts @@ -2,32 +2,9 @@ import { ApiClient } from './client'; import { isDOI } from '@/utils/doi'; import { Work, transformPaper } from '@/types/work'; -interface CreateByOpenAlexIdResponse { - paper_id: number; -} - -export interface UpdatePaperMetadataPayload { - title?: string; - doi?: string; - publishedDate?: string; // Will be converted to YYYY-MM-DD format - hubs?: number[]; - license?: string; -} - -export interface UpdatePaperAbstractPayload { - abstract: string; -} - export class PaperService { private static readonly BASE_PATH = '/api/paper'; - // TODO: Remove this - static async createByOpenAlexId(openalexId: string) { - return ApiClient.post(`${this.BASE_PATH}/create_by_openalex_id/`, { - openalex_id: openalexId, - }); - } - static async get(identifier: string): Promise { let response; @@ -39,47 +16,4 @@ export class PaperService { return transformPaper(response); } - - /** - * Update paper metadata - */ - static async updateMetadata(paperId: number, payload: UpdatePaperMetadataPayload): Promise { - const apiPayload: any = {}; - - if (payload.title) { - apiPayload.title = payload.title; - apiPayload.paper_title = payload.title; // Backend expects both fields - } - - if (payload.doi) { - apiPayload.doi = payload.doi; - } - - if (payload.publishedDate) { - // Convert from any date format to YYYY-MM-DD - const date = new Date(payload.publishedDate); - if (!isNaN(date.getTime())) { - apiPayload.paper_publish_date = date.toISOString().split('T')[0]; - } - } - - if (payload.hubs) { - apiPayload.hubs = payload.hubs; - } - - if (payload.license) { - apiPayload.pdf_license = payload.license; - } - - const response = await ApiClient.patch(`${this.BASE_PATH}/${paperId}/`, apiPayload); - return transformPaper(response); - } - - /** - * Update paper abstract - */ - static async updateAbstract(paperId: number, payload: UpdatePaperAbstractPayload): Promise { - const response = await ApiClient.patch(`${this.BASE_PATH}/${paperId}/`, payload); - return transformPaper(response); - } } diff --git a/types/search.ts b/types/search.ts index 1ed8cb5c6..8943bff07 100644 --- a/types/search.ts +++ b/types/search.ts @@ -71,7 +71,6 @@ export const transformSearchSuggestion = createTransformer