diff --git a/app/grant/[id]/[slug]/layout.tsx b/app/grant/[id]/[slug]/layout.tsx index ffc7bc641..9ed97032e 100644 --- a/app/grant/[id]/[slug]/layout.tsx +++ b/app/grant/[id]/[slug]/layout.tsx @@ -35,8 +35,6 @@ export async function generateMetadata({ params }: Props): Promise { modifiedTime: work.updatedDate, expirationTime: work.note?.post?.grant?.endDate, authors: work.authors.map((a) => a.authorProfile.fullName), - section: work.topics[0]?.name, - tags: work.topics.map((t) => t.name), }); } catch { return {}; diff --git a/app/paper/[id]/[slug]/layout.tsx b/app/paper/[id]/[slug]/layout.tsx index a8db9006e..4a73c5bba 100644 --- a/app/paper/[id]/[slug]/layout.tsx +++ b/app/paper/[id]/[slug]/layout.tsx @@ -31,8 +31,8 @@ export async function generateMetadata({ params }: Props): Promise { 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((t) => t.name), + section: work.topics?.[0]?.name, + tags: work.topics?.map((topic) => topic.name) ?? [], }); } catch { return {}; diff --git a/app/paper/[id]/create/version/UploadVersionForm.tsx b/app/paper/[id]/create/version/UploadVersionForm.tsx index 3002d6017..15b8faf31 100644 --- a/app/paper/[id]/create/version/UploadVersionForm.tsx +++ b/app/paper/[id]/create/version/UploadVersionForm.tsx @@ -51,7 +51,7 @@ export default function UploadVersionForm({ })) ); const [selectedHubs, setSelectedHubs] = useState(() => { - const sourceTopics = metadata?.topics ?? initialPaper.topics; + const sourceTopics = metadata?.topics ?? initialPaper.topics ?? []; return sourceTopics.map((topic) => ({ id: topic.id, name: topic.name, diff --git a/app/post/loading.tsx b/app/post/loading.tsx index 90b6b4d2c..fda852095 100644 --- a/app/post/loading.tsx +++ b/app/post/loading.tsx @@ -6,7 +6,7 @@ export default function PostLoading() { return ( } - rightSidebar={} + rightSidebar={} > diff --git a/app/proposal/[id]/[slug]/layout.tsx b/app/proposal/[id]/[slug]/layout.tsx index fe36ff340..282d7222c 100644 --- a/app/proposal/[id]/[slug]/layout.tsx +++ b/app/proposal/[id]/[slug]/layout.tsx @@ -35,8 +35,6 @@ export async function generateMetadata({ params }: Props): Promise { 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((t) => t.name), }), // Tokenized URL — private proposal, keep it out of search indexes. ...(shareToken ? { robots: { index: false, follow: false } } : {}), diff --git a/app/question/loading.tsx b/app/question/loading.tsx index 8cf7ab0f3..edcd8607f 100644 --- a/app/question/loading.tsx +++ b/app/question/loading.tsx @@ -6,7 +6,7 @@ export default function QuestionLoading() { return ( } - rightSidebar={} + rightSidebar={} > diff --git a/app/report/[id]/[slug]/layout.tsx b/app/report/[id]/[slug]/layout.tsx index 3cde66837..5f9d5f362 100644 --- a/app/report/[id]/[slug]/layout.tsx +++ b/app/report/[id]/[slug]/layout.tsx @@ -39,8 +39,6 @@ export async function generateMetadata({ params }: Props): Promise { publishedTime: work.publishedDate || work.createdDate, modifiedTime: work.updatedDate, authors: work.authors.map((author) => author.authorProfile.fullName), - section: work.topics[0]?.name, - tags: work.topics.map((topic) => topic.name), }); } diff --git a/components/Funding/GrantActionBar.tsx b/components/Funding/GrantActionBar.tsx index ec214c55a..cbf871eb0 100644 --- a/components/Funding/GrantActionBar.tsx +++ b/components/Funding/GrantActionBar.tsx @@ -33,7 +33,6 @@ export function GrantActionBar({ work, className }: GrantActionBarProps) { const { vote, isVoting } = useVote({ votableEntityId: work.id, feedContentType: 'POST', - relatedDocumentTopics: work.topics, relatedDocumentId: work.id.toString(), relatedDocumentContentType: work.contentType, }); diff --git a/components/Notebook/PublishingForm/components/TopicsSection.tsx b/components/Notebook/PublishingForm/components/TopicsSection.tsx deleted file mode 100644 index 56ae72cda..000000000 --- a/components/Notebook/PublishingForm/components/TopicsSection.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useFormContext } from 'react-hook-form'; -import { Tag } from 'lucide-react'; -import { SectionHeader } from './SectionHeader'; -import { SearchableMultiSelect } from '@/components/ui/form/SearchableMultiSelect'; -import { useCallback } from 'react'; -import { HubService } from '@/services/hub.service'; -import { getFieldErrorMessage } from '@/utils/form'; - -export function TopicsSection() { - const { - watch, - setValue, - formState: { errors }, - } = useFormContext(); - - const topics = watch('topics') || []; - - const handleSearch = useCallback(async (query: string) => { - const results = await HubService.suggestTopics(query); - return results.map((topic) => ({ - value: topic.id.toString(), - label: topic.name, - })); - }, []); - - return ( -
- Topics - setValue('topics', newTopics, { shouldValidate: true })} - onAsyncSearch={handleSearch} - placeholder="Search topics..." - debounceMs={500} - error={getFieldErrorMessage(errors.topics, 'Invalid topics')} - /> -
- ); -} diff --git a/components/Notebook/PublishingForm/index.tsx b/components/Notebook/PublishingForm/index.tsx index c068d6449..8546d26e4 100644 --- a/components/Notebook/PublishingForm/index.tsx +++ b/components/Notebook/PublishingForm/index.tsx @@ -6,7 +6,6 @@ import { WorkImageSection } from './components/WorkImageSection'; import { FundingSection } from './components/FundingSection'; import { AuthorsSection } from './components/AuthorsSection'; import { ContactsSection } from './components/ContactsSection'; -import { TopicsSection } from './components/TopicsSection'; import { GrantDescriptionSection } from './components/GrantDescriptionSection'; import { GrantOrganizationSection } from './components/GrantOrganizationSection'; import { GrantFundingAmountSection } from './components/GrantFundingAmountSection'; @@ -100,7 +99,6 @@ const getButtonText = ({ const FORM_DEFAULTS = { authors: [], contacts: [], - topics: [], rewardFunders: false, nftSupply: '1000', budget: '', @@ -171,12 +169,6 @@ const populateFromPost = (post: any, setValue: (name: any, value: any) => void) if (post.image) { setValue('coverImage', { file: null, url: post.image }); } - if (post.topics?.length > 0) { - setValue( - 'topics', - post.topics.map((t: any) => ({ value: t.id.toString(), label: t.name })) - ); - } if (post.authors?.length > 0) { setValue( 'authors', @@ -199,12 +191,6 @@ const populateFormFromNoteDetails = ( if (note.image || note.previewImage) { setValue('coverImage', { file: null, key: note.image, url: note.previewImage }); } - if (note.topics?.length) { - setValue( - 'topics', - note.topics.map((topic) => ({ value: topic.id.toString(), label: topic.name })) - ); - } if (note.authors?.length) { setValue( 'authors', @@ -242,20 +228,13 @@ const populateFormFromNoteDetails = ( } }; -/** Fills the gaps a Registered Report's proposal covers, which are ids without labels. */ +/** Fills the author gap from a Registered Report's proposal. */ const populateRegisteredReportPrefill = ( note: NoteWithContent, getValues: (name: any) => any, setValue: (name: any, value: any) => void ) => { - const { topicIds = [], authorIds = [] } = note.registeredReportPrefill ?? {}; - - if (topicIds.length > 0 && getValues('topics').length === 0) { - setValue( - 'topics', - topicIds.map((id) => ({ value: id.toString(), label: `Topic ${id}` })) - ); - } + const { authorIds = [] } = note.registeredReportPrefill ?? {}; if (authorIds.length > 0 && getValues('authors').length === 0) { setValue( @@ -283,8 +262,6 @@ const buildNoteDetailsUpdate = ( return { image: values.coverImage?.key ?? '', previewImage: values.coverImage?.url ?? '' }; case 'authors': return { authorIds: mapOptionsToIds(values.authors) }; - case 'topics': - return { hubIds: mapOptionsToIds(values.topics) }; case 'contacts': return isGrant ? { grantSettings: { contactIds: mapOptionsToIds(values.contacts) } } : null; case 'organization': @@ -684,7 +661,6 @@ export function PublishingForm({ fullJSON, fullSrc: html || '', assignDOI: !formData.workId, - topics: formData.topics.map((topic) => topic.value), authors: mapOptionsToIds(formData.authors), contacts: mapOptionsToIds(formData.contacts), articleType: ARTICLE_TYPE_API_MAP[formData.articleType] ?? 'DISCUSSION', @@ -840,7 +816,6 @@ export function PublishingForm({ )} {articleType === 'grant' ? : } - {note.post?.doi && (
diff --git a/components/Notebook/PublishingForm/schema.ts b/components/Notebook/PublishingForm/schema.ts index dd3259836..1c633b171 100644 --- a/components/Notebook/PublishingForm/schema.ts +++ b/components/Notebook/PublishingForm/schema.ts @@ -42,7 +42,6 @@ export const publishingFormSchema = z }), authors: z.array(optionSchema), contacts: z.array(optionSchema), - topics: z.array(optionSchema), budget: z.string().optional(), rewardFunders: z.boolean(), nftArt: z.any().nullable(), @@ -70,10 +69,6 @@ export const publishingFormSchema = z isPublic: z.boolean().optional(), }) .superRefine((data, ctx) => { - if (data.topics.length === 0) { - addIssue(ctx, 'topics', 'At least one topic is required'); - } - if (data.articleType === 'grant') { if (data.contacts.length === 0) { addIssue(ctx, 'contacts', 'At least one contact is required for grants'); diff --git a/components/modals/QuestionEditModal.tsx b/components/modals/QuestionEditModal.tsx index ea248d092..447cf448e 100644 --- a/components/modals/QuestionEditModal.tsx +++ b/components/modals/QuestionEditModal.tsx @@ -1,13 +1,11 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { BaseModal } from '@/components/ui/BaseModal'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/form/Input'; import { CommentEditor } from '@/components/Comment/CommentEditor'; -import { CommentContent } from '@/components/Comment/lib/types'; import { SessionProvider, useSession } from 'next-auth/react'; -import { HubsSelector, Hub } from '@/components/Paper/forms/HubsSelector'; import { Work } from '@/types/work'; import { PostService } from '@/services/post.service'; import { toast } from 'react-hot-toast'; @@ -29,19 +27,6 @@ export const QuestionEditModal = ({ isOpen, onClose, work }: QuestionEditModalPr const [title, setTitle] = useState(work.title); const [plainText, setPlainText] = useState(''); const [htmlContent, setHtmlContent] = useState(''); - const [selectedHubs, setSelectedHubs] = useState([]); - - // Initialize hubs from work topics - useEffect(() => { - if (work.topics && work.topics.length > 0) { - const hubs = work.topics.map((topic) => ({ - id: topic.id.toString(), - name: topic.name, - slug: topic.slug || '', - })); - setSelectedHubs(hubs); - } - }, [work.topics]); const handleSave = async () => { if (!title.trim()) { @@ -52,11 +37,6 @@ export const QuestionEditModal = ({ isOpen, onClose, work }: QuestionEditModalPr // For editing existing questions, we don't require plainText if it hasn't been changed // The content is already stored in work.previewContent - if (selectedHubs.length === 0) { - toast.error('Please select at least one topic'); - return; - } - withVerification(async () => { setIsSaving(true); try { @@ -66,7 +46,6 @@ export const QuestionEditModal = ({ isOpen, onClose, work }: QuestionEditModalPr document_type: work.contentType === 'preregistration' ? 'PREREGISTRATION' : 'QUESTION', full_src: htmlContent || work.previewContent || '', renderable_text: plainText || work.title || '', - hubs: selectedHubs.map((h) => Number(h.id)), title: title, }; @@ -138,16 +117,6 @@ export const QuestionEditModal = ({ isOpen, onClose, work }: QuestionEditModalPr }} />
- - {/* Topics */} -
- -
); diff --git a/components/skeletons/PublishingFormSkeleton.tsx b/components/skeletons/PublishingFormSkeleton.tsx index 1746d6656..019aa666f 100644 --- a/components/skeletons/PublishingFormSkeleton.tsx +++ b/components/skeletons/PublishingFormSkeleton.tsx @@ -20,14 +20,6 @@ export function PublishingFormSkeleton() { - {/* Topics Section */} -
-
-
-
-
-
- {/* Journal Section */}
diff --git a/components/work/ProposalSidebar.tsx b/components/work/ProposalSidebar.tsx index e7b7cbfb8..4082b8931 100644 --- a/components/work/ProposalSidebar.tsx +++ b/components/work/ProposalSidebar.tsx @@ -3,7 +3,6 @@ import { WorkMetadata } from '@/services/metadata.service'; import { getShareToken } from '@/lib/shareToken/server'; import { FundraiseSection } from './components/FundraiseSection'; import { FundingOpportunitySection } from './components/FundingOpportunitySection'; -import { TopicsSection } from './components/TopicsSection'; import { NonprofitSection } from './components/NonprofitSection'; import { FundersSection } from './components/FundersSection'; import { PeerReviewsSection } from './components/PeerReviewsSection'; @@ -46,7 +45,6 @@ export const ProposalSidebar = async ({ work, metadata }: ProposalSidebarProps) )} {work.linkedGrant && } {metadata.fundraising && } - {work.doi && }
); diff --git a/components/work/ProposalSidebarSkeleton.tsx b/components/work/ProposalSidebarSkeleton.tsx index 9cb660e45..dbff9339f 100644 --- a/components/work/ProposalSidebarSkeleton.tsx +++ b/components/work/ProposalSidebarSkeleton.tsx @@ -64,16 +64,6 @@ export const ProposalSidebarSkeleton = () => {
- {/* Topics */} -
-
-
-
-
-
-
-
- {/* DOI */}
diff --git a/components/work/RegisteredReportSidebar.tsx b/components/work/RegisteredReportSidebar.tsx index f623f0994..89170e633 100644 --- a/components/work/RegisteredReportSidebar.tsx +++ b/components/work/RegisteredReportSidebar.tsx @@ -1,7 +1,6 @@ import { SidebarHeader } from '@/components/ui/SidebarHeader'; import { DOISection } from './components/DOISection'; import { PeerReviewsSection } from './components/PeerReviewsSection'; -import { TopicsSection } from './components/TopicsSection'; import type { RegisteredReportProposalDetails } from '@/types/registeredReport'; interface RegisteredReportSidebarProps { @@ -34,7 +33,6 @@ export function RegisteredReportSidebar({ {reviewsUrl && ( )} - {reportDoi && }
); diff --git a/components/work/WorkHeader/WorkHeaderHooks.tsx b/components/work/WorkHeader/WorkHeaderHooks.tsx index 395e980a2..1dc4b436b 100644 --- a/components/work/WorkHeader/WorkHeaderHooks.tsx +++ b/components/work/WorkHeader/WorkHeaderHooks.tsx @@ -10,7 +10,7 @@ export function useWorkVote(work: Work) { const { vote, isVoting } = useVote({ votableEntityId: work.id, feedContentType: work.contentType === 'paper' ? 'PAPER' : 'POST', - relatedDocumentTopics: work.topics, + relatedDocumentTopics: work.contentType === 'paper' ? work.topics : undefined, relatedDocumentId: work.id.toString(), relatedDocumentContentType: work.contentType, }); diff --git a/components/work/WorkPrimaryActions.tsx b/components/work/WorkPrimaryActions.tsx index 85f4b4aee..0388340db 100644 --- a/components/work/WorkPrimaryActions.tsx +++ b/components/work/WorkPrimaryActions.tsx @@ -67,7 +67,7 @@ export const WorkPrimaryActions = ({ const { vote, isVoting } = useVote({ votableEntityId: work.id, feedContentType: work.contentType === 'paper' ? 'PAPER' : 'POST', - relatedDocumentTopics: work.topics, + relatedDocumentTopics: work.contentType === 'paper' ? work.topics : undefined, relatedDocumentId: work.id.toString(), relatedDocumentContentType: work.contentType, }); diff --git a/components/work/WorkRightSidebar.tsx b/components/work/WorkRightSidebar.tsx index b7c269c47..b9c1966f4 100644 --- a/components/work/WorkRightSidebar.tsx +++ b/components/work/WorkRightSidebar.tsx @@ -40,7 +40,7 @@ export const WorkRightSidebar = ({ work, metadata }: WorkRightSidebarProps) => { })} /> )} - + {work.contentType === 'paper' && } {work.doi && } {work.postType !== 'QUESTION' && } diff --git a/components/work/WorkRightSidebarSkeleton.tsx b/components/work/WorkRightSidebarSkeleton.tsx index 5a34d1a47..c6dbfaea8 100644 --- a/components/work/WorkRightSidebarSkeleton.tsx +++ b/components/work/WorkRightSidebarSkeleton.tsx @@ -1,17 +1,22 @@ 'use client'; -export const WorkRightSidebarSkeleton = () => { +interface WorkRightSidebarSkeletonProps { + showTopics?: boolean; +} + +export const WorkRightSidebarSkeleton = ({ showTopics = true }: WorkRightSidebarSkeletonProps) => { return (
- {/* Topics Section */} -
-
-
-
-
-
+ {showTopics && ( +
+
+
+
+
+
+
-
+ )} {/* DOI Section */}
diff --git a/components/work/registeredReportRouteServer.ts b/components/work/registeredReportRouteServer.ts index 874bc8e21..a0e88a0dd 100644 --- a/components/work/registeredReportRouteServer.ts +++ b/components/work/registeredReportRouteServer.ts @@ -66,7 +66,6 @@ export async function getRegisteredReportMetadata( const fallbackMetadata: WorkMetadata = { id: documentId ?? work.id, score: work.metrics?.votes ?? 0, - topics: work.topics ?? [], metrics: { votes: work.metrics?.votes ?? 0, comments: work.metrics?.comments ?? 0, diff --git a/hooks/useDocument.ts b/hooks/useDocument.ts index 234333b0f..de09aabdc 100644 --- a/hooks/useDocument.ts +++ b/hooks/useDocument.ts @@ -10,7 +10,6 @@ export interface PreregistrationPostParams { budget: string; rewardFunders: boolean; nftSupply: string; - topics: string[]; // Document related articleType: 'PREREGISTRATION' | 'DISCUSSION' | 'GRANT' | 'REGISTERED_REPORT'; @@ -82,7 +81,6 @@ export const useUpsertPost = (): UseUpsertPostReturn => { postParams.articleType === 'REGISTERED_REPORT' ? undefined : (postParams.assignDOI ?? false), - hubs: postParams.topics, authors: postParams.authors, }; diff --git a/lib/metadata-helpers.ts b/lib/metadata-helpers.ts index 50f1d531c..e4c218486 100644 --- a/lib/metadata-helpers.ts +++ b/lib/metadata-helpers.ts @@ -15,6 +15,7 @@ export function getWorkMetadata({ const title = `${work.title}${titleSuffix ? ` - ${titleSuffix}` : ''}`; const previewText = stripHtml(work.previewContent || '').substring(0, 155); const description = work.abstract || previewText || work.title; + const topics = work.contentType === 'paper' ? work.topics : undefined; const structuredData = generateDocumentStructuredData({ ...work, @@ -32,8 +33,8 @@ export function getWorkMetadata({ 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((t) => t.name) || [], + section: topics?.[0]?.name, + tags: topics?.map((topic) => topic.name), }); return { diff --git a/services/metadata.service.ts b/services/metadata.service.ts index 0a8ab2533..519140a34 100644 --- a/services/metadata.service.ts +++ b/services/metadata.service.ts @@ -11,7 +11,7 @@ import { countActiveBounties, countClosedBounties } from '@/components/Bounty/li export interface WorkMetadata { id: number; score: number; - topics: Topic[]; + topics?: Topic[]; metrics: ContentMetrics; fundraising?: Fundraise; bounties: Bounty[]; @@ -20,8 +20,9 @@ export interface WorkMetadata { } function transformWorkMetadata(response: any): WorkMetadata { - // Handle both array and object document structures - const document = Array.isArray(response.documents) ? response.documents[0] : response.documents; + // Post documents are arrays; paper documents are objects. + const isPaper = !Array.isArray(response.documents); + const document = isPaper ? response.documents : response.documents[0]; // Transform bounties if they exist using the existing transformer const bounties = document.bounties?.map((bounty: any) => transformBounty(bounty)) || []; @@ -32,9 +33,11 @@ function transformWorkMetadata(response: any): WorkMetadata { return { id: response.id, score: response.score, - topics: response.hubs - .filter((hub: any) => hub.namespace !== 'journal') - .map((hub: any) => transformTopic(hub)), + ...(isPaper && { + topics: response.hubs + .filter((hub: any) => hub.namespace !== 'journal') + .map((hub: any) => transformTopic(hub)), + }), metrics: { votes: response.score, comments: document.discussion_aggregates.discussion_count, diff --git a/smoke/proposal.spec.ts b/smoke/proposal.spec.ts index 246161953..4b33499ea 100644 --- a/smoke/proposal.spec.ts +++ b/smoke/proposal.spec.ts @@ -71,26 +71,6 @@ test('a new proposal can be drafted from an RFP and published', async ({ page }) await page.getByTestId('notebook-add-details').click(); - // Topics, funding goal and cover image are all required by the form's - // schema. Authors are not filled here: the form adds the current user on - // its own for a new proposal. - // - // The topic lookup is scoped to its own section on purpose. Unscoped, - // `option` also matches the currency