diff --git a/app/layouts/PublishMenu.tsx b/app/layouts/PublishMenu.tsx
index dceae1b10..f1675515b 100644
--- a/app/layouts/PublishMenu.tsx
+++ b/app/layouts/PublishMenu.tsx
@@ -2,22 +2,31 @@
import { ChevronRight, Plus } from 'lucide-react';
import { useRouter } from 'next/navigation';
+import dynamic from 'next/dynamic';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBullhorn, faFileSignature } from '@fortawesome/pro-light-svg-icons';
import { BaseMenu, BaseMenuItem } from '@/components/ui/form/BaseMenu';
import { useAuthenticatedAction } from '@/contexts/AuthModalContext';
import { SwipeableDrawer } from '@/components/ui/SwipeableDrawer';
-import {
- OpenFundingOpportunityModal,
- type FundingOpportunityCreationMethod,
-} from '@/components/Funding/OpenFundingOpportunityModal';
-import {
- OpenProposalModal,
- type ProposalCreationMethod,
-} from '@/components/Funding/OpenProposalModal';
+import type { FundingOpportunityCreationMethod } from '@/components/Funding/OpenFundingOpportunityModal';
+import type { ProposalCreationMethod } from '@/components/Funding/OpenProposalModal';
import { useScreenSize } from '@/hooks/useScreenSize';
import { useState } from 'react';
+const OpenFundingOpportunityModal = dynamic(
+ () =>
+ import('@/components/Funding/OpenFundingOpportunityModal').then(
+ (mod) => mod.OpenFundingOpportunityModal
+ ),
+ { ssr: false }
+);
+
+const OpenProposalModal = dynamic(
+ () =>
+ import('@/components/Funding/OpenProposalModal').then((mod) => mod.OpenProposalModal),
+ { ssr: false }
+);
+
interface PublishMenuProps {
forceMinimize?: boolean;
}
diff --git a/components/Funding/OpenFundingOpportunityModal.tsx b/components/Funding/OpenFundingOpportunityModal.tsx
index 2a1bd9143..ecba3f4c9 100644
--- a/components/Funding/OpenFundingOpportunityModal.tsx
+++ b/components/Funding/OpenFundingOpportunityModal.tsx
@@ -1,6 +1,7 @@
'use client';
import { useEffect, useState, useTransition } from 'react';
+import dynamic from 'next/dynamic';
import Link from 'next/link';
import { Dialog } from '@headlessui/react';
import {
@@ -19,9 +20,13 @@ import { Button, buttonVariants } from '@/components/ui/Button';
import Icon from '@/components/ui/icons/Icon';
import { ResearchCoinIcon } from '@/components/ui/icons/ResearchCoinIcon';
import AnimatedGlobe from '@/components/Globe/AnimatedGlobe';
-import { DocumentUploadStep } from '@/components/Funding/DocumentUploadStep';
import { cn } from '@/utils/styles';
+const DocumentUploadStep = dynamic(
+ () => import('@/components/Funding/DocumentUploadStep').then((mod) => mod.DocumentUploadStep),
+ { ssr: false }
+);
+
export type FundingOpportunityCreationMethod = 'template' | 'upload' | 'blank';
interface OpenFundingOpportunityModalProps {
diff --git a/components/Funding/OpenProposalModal.tsx b/components/Funding/OpenProposalModal.tsx
index 55fdb2e20..1dede7d54 100644
--- a/components/Funding/OpenProposalModal.tsx
+++ b/components/Funding/OpenProposalModal.tsx
@@ -1,14 +1,19 @@
'use client';
import { useEffect, useState } from 'react';
+import dynamic from 'next/dynamic';
import { Dialog } from '@headlessui/react';
import { ArrowLeft, ArrowRight, File, FileText, Globe, Lock, Upload, Users, X } from 'lucide-react';
import { BaseModal } from '@/components/ui/BaseModal';
import { Button } from '@/components/ui/Button';
import AnimatedProposal from '@/components/Proposal/AnimatedProposal';
-import { DocumentUploadStep } from '@/components/Funding/DocumentUploadStep';
import { cn } from '@/utils/styles';
+const DocumentUploadStep = dynamic(
+ () => import('@/components/Funding/DocumentUploadStep').then((mod) => mod.DocumentUploadStep),
+ { ssr: false }
+);
+
export type ProposalCreationMethod = 'template' | 'upload' | 'blank';
interface OpenProposalModalProps {
diff --git a/components/banners/VerificationBanner.tsx b/components/banners/VerificationBanner.tsx
index 5ca461887..fd12754d1 100644
--- a/components/banners/VerificationBanner.tsx
+++ b/components/banners/VerificationBanner.tsx
@@ -27,11 +27,11 @@ export default function VerificationBanner({ onClose, onMenuClose }: Verificatio
✓
- Auto sync all of your papers
+ Get a verified badge
✓
- Get a verified badge
+ Faster withdrawal limits
✓
diff --git a/components/modals/Verification/AddPublicationsForm.tsx b/components/modals/Verification/AddPublicationsForm.tsx
deleted file mode 100644
index d5ac6ea62..000000000
--- a/components/modals/Verification/AddPublicationsForm.tsx
+++ /dev/null
@@ -1,400 +0,0 @@
-'use client';
-
-import React, { useEffect, useState } from 'react';
-import { useUser } from '@/contexts/UserContext';
-import { useWebSocket } from '@/hooks/useWebSocket';
-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';
-import { Checkbox } from '@/components/ui/form/Checkbox';
-import { usePublicationsSearch, useAddPublications } from '@/hooks/usePublications';
-import { PublicationError } from '@/services/publication.service';
-import { Avatar } from '@/components/ui/Avatar';
-import { VerificationPaperResult } from './VerificationPaperResult';
-
-export type STEP =
- | 'DOI'
- | 'NEEDS_AUTHOR_CONFIRMATION'
- | 'RESULTS'
- | 'ERROR'
- | 'LOADING'
- | 'FINISHED';
-
-type ERROR_TYPE = 'DOI_NOT_FOUND' | 'GENERIC_ERROR' | null;
-
-export const ORDERED_STEPS: Array = [
- 'DOI',
- 'NEEDS_AUTHOR_CONFIRMATION',
- 'RESULTS',
- 'LOADING',
- 'FINISHED',
-];
-
-interface AddPublicationsFormProps {
- onStepChange?: ({ step }: { step: STEP }) => void;
- onDoThisLater?: () => void;
- allowDoThisLater?: boolean;
-}
-
-export function AddPublicationsForm({
- onStepChange,
- onDoThisLater,
- allowDoThisLater = false,
-}: AddPublicationsFormProps) {
- const [paperDoi, setPaperDoi] = useState('');
- const [selectedPaperIds, setSelectedPaperIds] = useState>([]);
- const [step, setStep] = useState('DOI');
- const [error, setError] = useState(null);
-
- const { user } = useUser();
-
- // Use our custom hooks
- const [{ data, isLoading: isSearchLoading, error: searchError }, searchPublications] =
- usePublicationsSearch();
-
- const [selectedAuthorId, setSelectedAuthorId] = useState(null);
-
- const [{ isLoading: isAddingLoading, error: addError }, addPublications] = useAddPublications();
-
- // Connect to WebSocket for publication status updates
- const { messages } = useWebSocket({
- url: user?.id ? WS_ROUTES.NOTIFICATIONS(user.id) : '',
- authRequired: true,
- autoConnect: !!user?.id,
- global: true,
- });
-
- // Handle WebSocket messages
- useEffect(() => {
- if (messages.length > 0) {
- const latestMessage = messages[messages.length - 1];
- const latestNotification = transformNotification(latestMessage);
-
- if (latestNotification.type === 'PUBLICATIONS_ADDED') {
- setStep('FINISHED');
- }
- }
- }, [messages]);
-
- // Notify parent component when step changes and reset error
- useEffect(() => {
- onStepChange?.({ step });
- setError(null);
- }, [step, onStepChange]);
-
- // Handle errors from hooks
- useEffect(() => {
- if (searchError) {
- if (searchError instanceof PublicationError && searchError.code === 'DOI_NOT_FOUND') {
- setError('DOI_NOT_FOUND');
- } else {
- setError('GENERIC_ERROR');
- }
- }
- }, [searchError]);
-
- useEffect(() => {
- if (addError) {
- setError('GENERIC_ERROR');
- setStep('ERROR');
- }
- }, [addError]);
-
- // Update step based on search results
- useEffect(() => {
- if (data) {
- if (!data.selectedAuthorId) {
- setStep('NEEDS_AUTHOR_CONFIRMATION');
- } else if (data.selectedAuthorId && (data.works || []).length > 0) {
- setStep('RESULTS');
- }
-
- // Set the first publication as selected if it matches the DOI
- if ((data.works || []).length > 0 && selectedPaperIds.length === 0) {
- setSelectedPaperIds([(data.works || [])[0].id]);
- }
- }
- }, [data]);
-
- const handleFetchPublications = async ({
- doi,
- authorId,
- }: {
- doi: string;
- authorId?: string | null;
- }) => {
- setError(null);
- try {
- await searchPublications({ doi, authorId });
- } catch (err) {
- // Error handling is done in the useEffect above
- }
- };
-
- const handleDoThisLater = () => {
- onDoThisLater?.();
- toast.success('Visit the "Publications" tab on your profile to resume', {
- duration: 5000,
- });
- };
-
- const handleAddPublications = async () => {
- setStep('LOADING');
- try {
- await addPublications({
- authorId: String(user?.authorProfile?.id) || '',
- openAlexPublicationIds: selectedPaperIds,
- openAlexAuthorId: selectedAuthorId || '',
- });
- // The actual completion will be handled by the WebSocket notification
- } catch (err) {
- // Error handling is done in the useEffect above
- }
- };
-
- const toggleSelectAll = () => {
- if (selectedPaperIds.length === (data?.works || []).length) {
- setSelectedPaperIds([]);
- } else {
- setSelectedPaperIds((data?.works || []).map((pub) => pub.id));
- }
- };
-
- const togglePaperSelection = (paperId: string) => {
- if (selectedPaperIds.includes(paperId)) {
- setSelectedPaperIds(selectedPaperIds.filter((id) => id !== paperId));
- } else {
- setSelectedPaperIds([...selectedPaperIds, paperId]);
- }
- };
-
- return (
-
- {step === 'DOI' && (
-
-
- setPaperDoi(e.target.value)}
- className="w-full"
- error={(() => {
- switch (error) {
- case 'DOI_NOT_FOUND':
- return "We couldn't find this DOI. Please check that you've entered it correctly or try a different publication.";
- case 'GENERIC_ERROR':
- return 'An error occurred. Please enter a valid DOI and try again.';
- default:
- return undefined;
- }
- })()}
- />
-
-
-
- {allowDoThisLater && (
-
- Do this later
-
- )}
- handleFetchPublications({ doi: paperDoi })}
- disabled={!paperDoi.trim() || isSearchLoading}
- className="ml-auto"
- >
- {isSearchLoading ? : null}
- Continue
-
-
-
- )}
-
- {step === 'NEEDS_AUTHOR_CONFIRMATION' && (
-
-
-
Please confirm which of these authors you are:
-
-
-
- {selectedAuthorId
- ? (data?.availableAuthors || []).find(
- (author) => author.id === selectedAuthorId
- )?.displayName
- : 'Select an author'}
-
-
-
- }
- >
- {(data?.availableAuthors || []).map((author) => (
- setSelectedAuthorId(author.id)}
- className={selectedAuthorId === author.id ? 'bg-gray-100' : ''}
- >
- {author.displayName}
-
- ))}
-
-
-
-
-
{
- setStep('DOI');
- setSelectedAuthorId(null);
- }}
- className="flex items-center gap-2"
- >
-
- Back
-
-
handleFetchPublications({ doi: paperDoi, authorId: selectedAuthorId })}
- disabled={!selectedAuthorId || isSearchLoading}
- >
- {isSearchLoading ? : null}
- Continue
-
-
-
- )}
-
- {step === 'RESULTS' && (
-
-
-
-
0
- }
- onCheckedChange={toggleSelectAll}
- className="[&>div>label]:text-indigo-600"
- />
-
- {selectedAuthorId && data?.availableAuthors && (
-
-
-
author.id === selectedAuthorId)
- ?.displayName || 'Author'
- }
- size="sm"
- />
-
- {
- data.availableAuthors.find((author) => author.id === selectedAuthorId)
- ?.displayName
- }
-
-
-
setStep('NEEDS_AUTHOR_CONFIRMATION')}
- className="text-xs text-indigo-600 hover:text-indigo-800"
- >
- (Change)
-
-
- )}
-
-
-
- {(data?.works || []).map((publication) => (
-
-
- {
- togglePaperSelection(publication.id);
- }}
- />
-
-
-
- ))}
-
-
-
-
-
{
- if ((data?.availableAuthors || []).length > 0) {
- setStep('NEEDS_AUTHOR_CONFIRMATION');
- } else {
- setStep('DOI');
- }
- }}
- className="flex items-center gap-2"
- >
-
- Back
-
-
- {isAddingLoading ? : null}
- Add Publications
-
-
-
- )}
-
- {step === 'LOADING' && (
-
-
-
-
-
Adding publications to your profile...
-
- This may take a few minutes. We will notify you when the process is complete. Feel free
- to close this popup.
-
-
- )}
-
- {step === 'ERROR' && (
-
-
-
-
-
Something went wrong
-
- An unexpected error has occurred. Please try again later.
-
-
setStep('DOI')}>Try Again
-
- )}
-
- );
-}
diff --git a/components/modals/Verification/VerificationPaperResult.tsx b/components/modals/Verification/VerificationPaperResult.tsx
deleted file mode 100644
index daa2e85a7..000000000
--- a/components/modals/Verification/VerificationPaperResult.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from 'react';
-import Link from 'next/link';
-import { Badge } from '@/components/ui/Badge';
-import { OpenAlexWork } from '@/types/publication';
-import { formatTimestamp } from '@/utils/date';
-
-interface Concept {
- id: string;
- displayName: string;
- slug?: string;
-}
-
-interface VerificationPaperResultProps {
- result: OpenAlexWork;
-}
-
-export const VerificationPaperResult = ({ result }: VerificationPaperResultProps) => {
- // Extract author names from authorships if available
- const authorNames = result.authorships?.map((a) => a.author.displayName) || [];
-
- // Filter important concepts
- const concepts = result.concepts
- ? result.concepts
- .filter((concept) => concept.level === 1)
- .sort((a, b) => b.relevancyScore - a.relevancyScore)
- .slice(0, 3)
- : [];
-
- return (
-
-
-
{result.title}
-
-
- {authorNames.length > 0 && (
-
- {authorNames[0]}
- {authorNames.length > 1 && ' et al.'}
-
- )}
-
- {result.publicationDate && (
- <>
-
-
{formatTimestamp(result.publicationDate)}
- >
- )}
-
- {result.doi && (
- <>
-
-
- {result.doiUrl ? (
-
- {result.doi}
-
- ) : (
- result.doi
- )}
-
- >
- )}
-
- {result.venue?.displayName && (
- <>
-
-
{result.venue.displayName}
- >
- )}
-
-
- {concepts && concepts.length > 0 && (
-
- {concepts.map((concept, index) => (
-
- {concept.displayName}
-
- ))}
-
- )}
-
- {result.authorshipPosition && (
-
- {result.authorshipPosition === 'first'
- ? 'First Author'
- : result.authorshipPosition === 'last'
- ? 'Last Author'
- : 'Co-Author'}
-
- )}
-
-
- );
-};
-
-export default VerificationPaperResult;
diff --git a/components/modals/VerifyIdentityModal.tsx b/components/modals/VerifyIdentityModal.tsx
index ff2961d8c..c635af0e3 100644
--- a/components/modals/VerifyIdentityModal.tsx
+++ b/components/modals/VerifyIdentityModal.tsx
@@ -2,21 +2,10 @@
import { Dialog, Transition } from '@headlessui/react';
import { Fragment, useState, useEffect } from 'react';
-import {
- X,
- Check,
- AlertTriangle,
- BadgeCheck,
- Users,
- GraduationCap,
- TrendingUp,
- CircleDollarSign,
-} from 'lucide-react';
+import { X, Check, AlertTriangle, BadgeCheck, Users, GraduationCap } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { useUser } from '@/contexts/UserContext';
import { VerificationWithPersonaStep } from './Verification/VerificationWithPersonaStep';
-import { AddPublicationsForm, STEP } from './Verification/AddPublicationsForm';
-import { ProgressStepper } from '@/components/ui/ProgressStepper';
import { navigateToAuthorProfile } from '@/utils/navigation';
import type { VerificationModalContext } from '@/contexts/VerificationContext';
@@ -31,15 +20,7 @@ type VerificationStep =
| 'INTRO'
| 'IDENTITY'
| 'IDENTITY_VERIFIED_SUCCESSFULLY'
- | 'IDENTITY_CANNOT_BE_VERIFIED'
- | 'PUBLICATIONS'
- | 'SUCCESS';
-
-const stepperSteps = [
- { id: 'IDENTITY', label: 'Verify Identity' },
- { id: 'PUBLICATIONS', label: 'Publication History' },
- { id: 'SUCCESS', label: 'View Rewards' },
-];
+ | 'IDENTITY_CANNOT_BE_VERIFIED';
export function VerifyIdentityModal({
isOpen,
@@ -48,7 +29,6 @@ export function VerifyIdentityModal({
context = null,
}: VerifyIdentityModalProps) {
const [currentStep, setCurrentStep] = useState(initialStep);
- const [publicationsSubstep, setPublicationsSubstep] = useState('DOI');
const { user } = useUser();
const isPublishContext = context === 'publish';
@@ -56,25 +36,12 @@ export function VerifyIdentityModal({
useEffect(() => {
if (isOpen) {
setCurrentStep(context === 'publish' ? 'IDENTITY' : initialStep);
- setPublicationsSubstep('DOI');
}
}, [isOpen, initialStep, context]);
const handleNext = () => {
if (currentStep === 'INTRO') {
setCurrentStep('IDENTITY');
- } else if (currentStep === 'IDENTITY') {
- setCurrentStep('PUBLICATIONS');
- } else if (currentStep === 'PUBLICATIONS') {
- // Send verification request via WebSocket
- if (user?.id) {
- // Placeholder for WebSocket sendMessage
- }
- } else if (currentStep === 'SUCCESS') {
- onClose();
- if (context !== 'publish') {
- navigateToAuthorProfile(user?.authorProfile?.id, false);
- }
}
};
@@ -86,6 +53,11 @@ export function VerifyIdentityModal({
}
};
+ const handleViewProfile = () => {
+ onClose();
+ navigateToAuthorProfile(user?.authorProfile?.id, false);
+ };
+
const renderStepContent = () => {
switch (currentStep) {
case 'INTRO':
@@ -220,7 +192,7 @@ export function VerifyIdentityModal({
);
}
- // General flow: continue to publications step
+
return (
@@ -237,19 +209,12 @@ export function VerifyIdentityModal({
- setCurrentStep('PUBLICATIONS')} className="w-full">
- Next: View rewards on my publications
-
- {
- onClose();
- navigateToAuthorProfile(user?.authorProfile?.id, false);
- }}
- className="w-full"
- >
+
View my profile
+
+ Done
+
);
@@ -281,94 +246,6 @@ export function VerifyIdentityModal({
);
-
- case 'PUBLICATIONS':
- return (
-
- {publicationsSubstep === 'DOI' && (
-
-
- Let's find rewards on your publications
-
-
- Enter a DOI for any paper you've published and we will fetch the rest of your
- works.
-
-
-
- What happens next
-
-
-
-
-
-
-
-
We will build your researcher profile
-
-
-
-
-
-
-
We will calculate your hub specific reputation
-
-
-
-
-
-
-
- We will identify your prior publications that are eligible for rewards
-
-
-
-
- )}
-
- {publicationsSubstep === 'RESULTS' && (
-
-
- Review your publication history
-
-
- We fetched some of your publications. We may have mislabeled a paper or two so
- please select only the ones that you have authored or co-authored.
-
-
- )}
-
-
{
- if (step === 'FINISHED') setCurrentStep('SUCCESS');
- else {
- setPublicationsSubstep(step);
- }
- }}
- onDoThisLater={onClose}
- allowDoThisLater={true}
- />
-
- );
-
- case 'SUCCESS':
- return (
-
-
-
Verification Successful!
-
- Your identity has been verified. You can now claim your publications and earn
- ResearchCoin for your contributions.
-
-
- View My Profile
-
-
- );
}
};
@@ -427,13 +304,6 @@ export function VerifyIdentityModal({
)}
- {/* Progress stepper */}
- {['PUBLICATIONS', 'SUCCESS'].includes(currentStep) && (
-
- )}
-
{/* Content */}
{renderStepContent()}
diff --git a/components/ui/ProgressStepper.tsx b/components/ui/ProgressStepper.tsx
deleted file mode 100644
index d1d7f6653..000000000
--- a/components/ui/ProgressStepper.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-'use client';
-
-import { ReactNode } from 'react';
-import { cn } from '@/utils/styles';
-
-export interface ProgressStepperStep {
- id: string;
- label: string;
- icon?: ReactNode;
-}
-
-interface ProgressStepperProps {
- steps: ProgressStepperStep[];
- currentStep: string;
- onStepClick?: (stepId: string) => void;
- className?: string;
-}
-
-export function ProgressStepper({
- steps,
- currentStep,
- onStepClick,
- className,
-}: ProgressStepperProps) {
- const currentStepIndex = steps.findIndex((step) => step.id === currentStep);
-
- return (
-
-
- {steps.map((step, index) => {
- const isActive = step.id === currentStep;
- const isCompleted = index < currentStepIndex;
- const isClickable = onStepClick && (isCompleted || index <= currentStepIndex + 1);
-
- return (
- isClickable && onStepClick(step.id)}
- >
-
-
- {step.icon || (isCompleted ? : index + 1)}
-
-
- {step.label}
-
-
- {index < steps.length - 1 && (
-
- )}
-
- );
- })}
-
-
- );
-}
-
-function CheckIcon({ className }: { className?: string }) {
- return (
-
-
-
- );
-}
diff --git a/hooks/usePublications.ts b/hooks/usePublications.ts
index 7eb63c265..5b9bcb0d7 100644
--- a/hooks/usePublications.ts
+++ b/hooks/usePublications.ts
@@ -1,109 +1,11 @@
'use client';
-import { useState, useCallback, useEffect } from 'react';
-import {
- PublicationService,
- PublicationSearchParams,
- PublicationError,
- AddPublicationsParams,
- AuthorPublicationsResponse,
-} from '@/services/publication.service';
-import { OpenAlexWork, OpenAlexAuthor, PublicationSearchResponse } from '@/types/publication';
+import { useState, useEffect } from 'react';
+import { PublicationService, AuthorPublicationsResponse } from '@/services/publication.service';
import { ID } from '@/types/root';
import { useFeedStateRestoration } from './useFeedStateRestoration';
import { FeedEntry } from '@/types/feed';
-interface UsePublicationsSearchState {
- data: PublicationSearchResponse | null;
- isLoading: boolean;
- error: Error | null;
-}
-
-type SearchPublicationsFn = (params: PublicationSearchParams) => Promise;
-type SetSelectedAuthorIdFn = (authorId: string | null) => void;
-type UsePublicationsSearchReturn = [UsePublicationsSearchState, SearchPublicationsFn];
-
-/**
- * Hook for searching publications by DOI
- */
-export function usePublicationsSearch(): UsePublicationsSearchReturn {
- const [data, setData] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const searchPublications = useCallback(async (params: PublicationSearchParams): Promise => {
- try {
- setIsLoading(true);
- setError(null);
-
- const transformedData = await PublicationService.searchPublications(params);
-
- // Set publications but put the publication that matches DOI first
- const foundIdx = transformedData.works.findIndex(
- (work) => work.doi?.includes(params.doi) || work.doiUrl?.includes(params.doi)
- );
-
- if (foundIdx > -1) {
- const publication = transformedData.works[foundIdx];
- transformedData.works.splice(foundIdx, 1);
- transformedData.works.unshift(publication);
- }
-
- setData(transformedData);
- } catch (err) {
- console.error('Error fetching publications:', err);
- setError(err instanceof Error ? err : new Error('Failed to search publications'));
- setData(null);
- } finally {
- setIsLoading(false);
- }
- }, []);
-
- return [
- {
- data,
- isLoading,
- error,
- },
- searchPublications,
- ];
-}
-
-interface UseAddPublicationsState {
- isLoading: boolean;
- error: Error | null;
-}
-
-type AddPublicationsFn = (params: AddPublicationsParams) => Promise;
-type UseAddPublicationsReturn = [UseAddPublicationsState, AddPublicationsFn];
-
-/**
- * Hook for adding publications to a user's profile
- */
-export function useAddPublications(): UseAddPublicationsReturn {
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const addPublications = useCallback(async (params: AddPublicationsParams): Promise => {
- try {
- setIsLoading(true);
- setError(null);
-
- await PublicationService.addPublications(params);
- } catch (err) {
- console.error('Error adding publications:', err);
- const errorMsg = err instanceof PublicationError ? err.message : 'Failed to add publications';
- const error = new Error(errorMsg);
- setError(error);
- throw error;
- } finally {
- setIsLoading(false);
- }
- }, []);
-
- return [{ isLoading, error }, addPublications];
-}
-
interface UseAuthorPublicationsOptions {
authorId: ID;
initialData?: AuthorPublicationsResponse;
diff --git a/next.config.js b/next.config.js
index 6d700ce87..69b68f509 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,3 +1,5 @@
+const path = require('node:path');
+
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
@@ -236,8 +238,18 @@ const nextConfig = {
turbopack: {
resolveAlias: {
'@': __dirname,
+ 'prosemirror-tables': './node_modules/prosemirror-tables',
+ 'prosemirror-state': './node_modules/prosemirror-state',
},
},
+ webpack: (config) => {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ 'prosemirror-tables': path.resolve(__dirname, 'node_modules/prosemirror-tables'),
+ 'prosemirror-state': path.resolve(__dirname, 'node_modules/prosemirror-state'),
+ };
+ return config;
+ },
experimental: {
scrollRestoration: true,
},
diff --git a/public/icons/verificationRequirements.svg b/public/icons/verificationRequirements.svg
deleted file mode 100644
index 8d2e95f8c..000000000
--- a/public/icons/verificationRequirements.svg
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Government-issued ID
-
-
-
-
-
-
-
-
-
-
-
- Camera access
-
-
\ No newline at end of file
diff --git a/services/paper.service.ts b/services/paper.service.ts
index a2a285c86..0f43049f2 100644
--- a/services/paper.service.ts
+++ b/services/paper.service.ts
@@ -2,10 +2,6 @@ import { ApiClient } from './client';
import { isDOI } from '@/utils/doi';
import { Work, transformPaper, ModerationStatus } from '@/types/work';
-interface CreateByOpenAlexIdResponse {
- paper_id: number;
-}
-
interface Author {
id: number;
isCorrespondingAuthor: boolean;
@@ -64,13 +60,6 @@ export interface UpdatePaperAbstractPayload {
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;
diff --git a/services/publication.service.ts b/services/publication.service.ts
index 85f06de32..3d6f9752d 100644
--- a/services/publication.service.ts
+++ b/services/publication.service.ts
@@ -1,5 +1,4 @@
import { ApiClient } from './client';
-import { transformPublicationsResponse, PublicationSearchResponse } from '@/types/publication';
import { ID } from '@/types/root';
import { ApiError } from './types';
@@ -13,17 +12,6 @@ export class PublicationError extends Error {
}
}
-export interface PublicationSearchParams {
- doi: string;
- authorId?: string | null;
-}
-
-export interface AddPublicationsParams {
- authorId: string;
- openAlexPublicationIds: string[];
- openAlexAuthorId: string;
-}
-
export interface GetAuthorPublicationsParams {
authorId: ID;
nextUrl?: string | null;
@@ -37,72 +25,8 @@ export interface AuthorPublicationsResponse {
}
export class PublicationService {
- private static readonly BASE_PATH = '/api/paper';
private static readonly AUTHOR_PATH = '/api/author';
- /**
- * Search for publications by DOI and optionally filter by author
- * @throws {PublicationError} When the request fails or parameters are invalid
- */
- static async searchPublications(
- params: PublicationSearchParams
- ): Promise {
- const { doi, authorId } = params;
-
- if (!doi) {
- throw new PublicationError('Missing DOI parameter', 'INVALID_PARAMS');
- }
-
- try {
- const queryParams = new URLSearchParams();
- queryParams.append('doi', doi);
- if (authorId) {
- queryParams.append('author_id', authorId);
- }
-
- const response = await ApiClient.get(
- `${this.BASE_PATH}/fetch_publications_by_doi?${queryParams.toString()}`
- );
- return transformPublicationsResponse(response);
- } catch (error) {
- console.log(error);
- if (error instanceof ApiError && error.status === 404) {
- throw new PublicationError('DOI not found', 'DOI_NOT_FOUND');
- }
-
- throw new PublicationError(
- 'Failed to search publications',
- error instanceof Error ? error.message : 'UNKNOWN_ERROR'
- );
- }
- }
-
- /**
- * Add publications to the user's profile
- * @throws {PublicationError} When the request fails or parameters are invalid
- */
- static async addPublications(params: AddPublicationsParams): Promise {
- if (!params.openAlexPublicationIds || params.openAlexPublicationIds.length === 0) {
- throw new PublicationError('No publication IDs provided', 'INVALID_PARAMS');
- }
-
- if (!params.openAlexAuthorId || !params.authorId) {
- throw new PublicationError('No author ID provided', 'INVALID_PARAMS');
- }
-
- try {
- await ApiClient.post(`${this.AUTHOR_PATH}/${params.authorId}/publications/`, {
- openalex_ids: params.openAlexPublicationIds,
- openalex_author_id: params.openAlexAuthorId,
- });
- } catch (error) {
- throw new PublicationError(
- 'Failed to add publications',
- error instanceof Error ? error.message : 'UNKNOWN_ERROR'
- );
- }
- }
-
/**
* Fetches publications for a specific author
* @param params - Parameters for fetching author publications
diff --git a/types/publication.ts b/types/publication.ts
index ff2d8eab1..3ae7d9e81 100644
--- a/types/publication.ts
+++ b/types/publication.ts
@@ -1,108 +1,5 @@
import { stripHtml } from '@/utils/stringUtils';
import { FeedEntry } from './feed';
-import { createTransformer } from './transformer';
-
-// Transformed types for our application
-export interface OpenAlexAuthor {
- id: string;
- displayName: string;
- orcid?: string;
-}
-
-export interface OpenAlexConcept {
- displayName: string;
- level: number;
- relevancyScore: number;
-}
-
-export interface OpenAlexWork {
- id: string;
- title: string;
- doi?: string;
- doiUrl?: string;
- publicationYear?: number;
- publicationDate?: string;
- authorshipPosition?: string;
- venue?: {
- displayName?: string;
- };
- authorships?: Array<{
- author: {
- id: string;
- displayName: string;
- };
- position?: string;
- }>;
- concepts: OpenAlexConcept[];
-}
-
-export interface PublicationSearchResponse {
- works: OpenAlexWork[];
- selectedAuthorId: string | null;
- availableAuthors: OpenAlexAuthor[];
-}
-
-// Create transformers using the utility function
-export const transformOpenAlexAuthor = createTransformer((raw) => ({
- id: raw.id,
- displayName: raw.display_name,
- orcid: raw.orcid,
-}));
-
-export const transformOpenAlexConcept = createTransformer((raw) => ({
- displayName: raw.display_name,
- level: raw.level,
- relevancyScore: raw.score,
-}));
-
-export const transformOpenAlexWork = createTransformer((raw) => ({
- id: raw.id,
- title: raw.title,
- doiUrl: raw.doi,
- doi: raw.doi ? raw.doi.replace('https://doi.org/', '') : undefined,
- publicationYear: raw.publication_year,
- publicationDate: raw.publication_date,
- authorshipPosition: raw.authorship_position,
- venue: raw.venue
- ? {
- displayName: raw.venue.display_name,
- }
- : undefined,
- authorships: raw.authorships?.map((authorship: any) => ({
- author: {
- id: authorship.author.id,
- displayName: authorship.author.display_name,
- },
- position: authorship.position,
- })),
- concepts: (raw.concepts || []).map(transformOpenAlexConcept),
-}));
-
-export const transformPublicationsResponse = createTransformer(
- (raw) => ({
- works: (raw.works || []).map(transformOpenAlexWork),
- selectedAuthorId: raw.selected_author_id,
- availableAuthors: (raw.available_authors || []).map(transformOpenAlexAuthor),
- })
-);
-
-export interface TransformedPublication {
- id: string;
- title: string;
- doi?: string;
- doiUrl?: string;
- publicationYear?: number;
- publicationDate?: string;
- venue?: {
- displayName?: string;
- };
- authors: Array<{
- id: string;
- displayName: string;
- position?: string;
- }>;
- concepts: OpenAlexConcept[];
-}
export interface AuthorPublicationsResponse {
count: number;