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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/author/[id]/components/Moderation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function ModerationSkeleton() {
return (
<div className="flex flex-col gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex flex-col gap-2 text-sm w-full md:max-w-[300px]">
<div className="flex flex-col gap-2 text-sm w-full md:!max-w-[300px]">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-center justify-between">
<span className="font-medium whitespace-nowrap bg-gray-200 rounded h-4 w-24 animate-pulse" />
Expand Down Expand Up @@ -232,7 +232,7 @@ export default function Moderation({ userId, authorId, refetchAuthorInfo }: Mode
</div>

<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex flex-col gap-2 text-sm w-full md:max-w-[300px]">
<div className="flex flex-col gap-2 text-sm w-full md:!max-w-[300px]">
<div className="flex items-center justify-between">
<span className="font-medium whitespace-nowrap">Email:</span>
<span className="truncate max-w-[200px]">{userDetails.email || 'N/A'}</span>
Expand Down
84 changes: 77 additions & 7 deletions app/author/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { use, useTransition } from 'react';
import { use, useTransition, useState } from 'react';
import { useAuthorAchievements, useAuthorInfo, useAuthorSummaryStats } from '@/hooks/useAuthor';
import { useUser } from '@/contexts/UserContext';
import { Card } from '@/components/ui/Card';
Expand All @@ -18,6 +18,10 @@ import AuthorProfile from './components/AuthorProfile';
import { useAuthorPublications } from '@/hooks/usePublications';
import { transformPublicationToFeedEntry } from '@/types/publication';
import PinnedFundraise from './components/PinnedFundraise';
import { BookOpen } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { toast } from 'react-hot-toast';
import { AddPublicationsModal } from '@/components/modals/AddPublicationsModal';

function toNumberOrNull(value: any): number | null {
if (value === '' || value === null || value === undefined) return null;
Expand Down Expand Up @@ -90,8 +94,18 @@ const TAB_TO_CONTRIBUTION_TYPE: Record<string, ContributionType> = {
bounties: 'BOUNTY',
};

function AuthorTabs({ authorId, userId }: { authorId: number; userId?: number }) {
function AuthorTabs({
authorId,
isOwnProfile,
userId,
}: {
authorId: number;
isOwnProfile: boolean;
userId?: number;
}) {
const [isPending, startTransition] = useTransition();
const [isAddPublicationsModalOpen, setIsAddPublicationsModalOpen] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const tabs = [
{ id: 'contributions', label: 'Overview' },
{ id: 'publications', label: 'Publications' },
Expand Down Expand Up @@ -126,10 +140,20 @@ function AuthorTabs({ authorId, userId }: { authorId: number; userId?: number })
hasMore: hasMorePublications,
loadMore: loadMorePublications,
isLoadingMore: isLoadingMorePublications,
refresh: refreshPublications,
} = useAuthorPublications({
authorId,
});

const handleRefreshPublications = async () => {
setIsRefreshing(true);
try {
await refreshPublications();
} finally {
setIsRefreshing(false);
}
};

const handleTabChange = (tabId: string) => {
startTransition(() => {
const params = new URLSearchParams(searchParams);
Expand All @@ -143,7 +167,6 @@ function AuthorTabs({ authorId, userId }: { authorId: number; userId?: number })
if (publicationsError) {
return <div>Error: {publicationsError.message}</div>;
}

// Filter out invalid publications
const validPublications = publications.filter((publication) => {
try {
Expand All @@ -157,23 +180,55 @@ function AuthorTabs({ authorId, userId }: { authorId: number; userId?: number })

return (
<div>
{/* Add Publications Button - only show for own profile */}
{isOwnProfile && validPublications.length > 0 && (
<div className="mb-6 flex justify-end">
<Button
onClick={() => setIsAddPublicationsModalOpen(true)}
className="flex items-center gap-2"
>
<BookOpen className="h-4 w-4" />
Add Publications
</Button>
</div>
)}

<FeedContent
entries={
isPending
isPending || isRefreshing
? []
: validPublications.map((publication) =>
transformPublicationToFeedEntry(publication)
)
}
isLoading={isPending || isPublicationsLoading}
isLoading={isPending || isPublicationsLoading || isRefreshing}
hasMore={hasMorePublications}
loadMore={loadMorePublications}
showBountyFooter={false}
hideActions={true}
isLoadingMore={isLoadingMorePublications}
showBountySupportAndCTAButtons={false}
showBountyDeadline={false}
noEntriesElement={<SearchEmpty title="No publications found." className="mb-10" />}
noEntriesElement={
isOwnProfile ? (
<div className="text-center py-8">
<BookOpen className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No publications yet</h3>
<p className="text-gray-600 mb-4">
Add your published papers to showcase your research contributions.
</p>
<Button
onClick={() => setIsAddPublicationsModalOpen(true)}
className="flex items-center gap-2 mx-auto"
>
<BookOpen className="h-4 w-4" />
Add Your First Publication
</Button>
</div>
) : (
<SearchEmpty title="No publications found." className="mb-10" />
)
}
maxLength={150}
/>
</div>
Expand Down Expand Up @@ -229,6 +284,16 @@ function AuthorTabs({ authorId, userId }: { authorId: number; userId?: number })
className="border-b"
/>
<div className="mt-6">{renderTabContent()}</div>

{/* Add Publications Modal */}
<AddPublicationsModal
isOpen={isAddPublicationsModalOpen}
onClose={() => setIsAddPublicationsModalOpen(false)}
onPublicationsAdded={() => {
toast.success('Publications added successfully!');
handleRefreshPublications(); // Use the wrapper function
}}
/>
</div>
);
}
Expand All @@ -239,6 +304,7 @@ export default function AuthorProfilePage({ params }: { params: Promise<{ id: st
const authorId = toNumberOrNull(resolvedParams.id);
const [{ author: user, isLoading, error }, refetchAuthorInfo] = useAuthorInfo(authorId);
const { user: currentUser } = useUser();

// Determine if current user is a hub editor
const isHubEditor = !!currentUser?.authorProfile?.isHubEditor;
const [{ achievements, isLoading: isAchievementsLoading, error: achievementsError }] =
Expand Down Expand Up @@ -304,7 +370,11 @@ export default function AuthorProfilePage({ params }: { params: Promise<{ id: st
</Card>
)}
</div>
<AuthorTabs authorId={user.authorProfile.id} userId={user.authorProfile.userId} />
<AuthorTabs
authorId={user.authorProfile.id}
userId={user.authorProfile.userId}
isOwnProfile={currentUser?.authorProfile?.id === user.authorProfile.id}
/>
</>
);
}
97 changes: 97 additions & 0 deletions components/modals/AddPublicationsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use client';

import { useState, useEffect } from 'react';
import { BookOpen } from 'lucide-react';
import { BaseModal } from '@/components/ui/BaseModal';
import { AddPublicationsForm, STEP } from './Verification/AddPublicationsForm';

interface AddPublicationsModalProps {
isOpen: boolean;
onClose: () => void;
onPublicationsAdded?: () => void;
}

export function AddPublicationsModal({
isOpen,
onClose,
onPublicationsAdded,
}: AddPublicationsModalProps) {
const [currentStep, setCurrentStep] = useState<STEP>('DOI');

useEffect(() => {
if (isOpen) {
setCurrentStep('DOI');
}
}, [isOpen]);

const handleStepChange = ({ step }: { step: STEP }) => {
console.log('handleStepChange', step);
setCurrentStep(step);

if (step === 'FINISHED') {
onPublicationsAdded?.();
onClose();
}
};

const getModalTitle = () => {
switch (currentStep) {
case 'DOI':
return 'Add Publications to Your Profile';
case 'NEEDS_AUTHOR_CONFIRMATION':
return 'Confirm Author Identity';
case 'RESULTS':
return 'Select Publications';
case 'LOADING':
return 'Adding Publications';
case 'ERROR':
return 'Error';
default:
return 'Add Publications';
}
};

const getModalDescription = () => {
switch (currentStep) {
case 'DOI':
return "Enter a DOI for any paper you've published and we will fetch your other works.";
case 'NEEDS_AUTHOR_CONFIRMATION':
return 'We found multiple authors for this publication. Please select which one is you.';
case 'RESULTS':
return 'Review and select the publications you want to add to your profile.';
case 'LOADING':
return 'We are processing your publications. This may take a few minutes.';
case 'ERROR':
return 'Something went wrong while adding your publications.';
default:
return '';
}
};

const headerAction = (
<div className="bg-indigo-100 p-2 rounded-lg">
<BookOpen className="h-5 w-5 text-indigo-600" />
</div>
);

return (
<BaseModal
isOpen={isOpen}
onClose={onClose}
title={getModalTitle()}
maxWidth="max-w-2xl"
headerAction={headerAction}
padding="p-6"
>
{getModalDescription() && (
<p className="text-sm text-gray-600 mb-6">{getModalDescription()}</p>
)}

<AddPublicationsForm
onStepChange={handleStepChange}
onDoThisLater={onClose}
allowDoThisLater={false}
/>
</BaseModal>
);
}
2 changes: 1 addition & 1 deletion components/modals/Verification/AddPublicationsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function AddPublicationsForm({
useEffect(() => {
onStepChange?.({ step });
setError(null);
}, [step, onStepChange]);
}, [step]);

// Handle errors from hooks
useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion components/ui/BaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const BaseModal: FC<BaseModalProps> = ({
// No rounded corners on mobile, rounded on md+
'md:!rounded-2xl',
// Only apply max width on md and up
`md:${maxWidth}`
`md:!${maxWidth}`
)}
style={{
display: 'flex',
Expand Down
15 changes: 0 additions & 15 deletions services/websocket.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,6 @@ export interface WebSocketOptions {
onError?: (error: Error) => void;
}

const ALLOWED_ORIGINS = [
'localhost',
'localhost:8000',
'ws://localhost:8000',
'ws://localhost:8000/',
'backend.prod.researchhub.com',
'wss://backend.prod.researchhub.com',
'backend.staging.researchhub.com',
'wss://backend.staging.researchhub.com',
'v2.staging.researchhub.com',
'wss://v2.staging.researchhub.com',
'researchhub.com',
'wss://researchhub.com',
];

const CLOSE_CODES = {
GOING_AWAY: 1001,
POLICY_VIOLATION: 1008,
Expand Down
16 changes: 15 additions & 1 deletion tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,21 @@ export default {
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
safelist: ['md:max-w-md', 'md:max-w-lg'],
safelist: [
'md:!max-w-xs',
'md:!max-w-md',
'md:!max-w-lg',
'md:!max-w-xl',
'md:!max-w-2xl',
'md:!max-w-3xl',
'md:!max-w-4xl',
'md:!max-w-5xl',
'md:!max-w-6xl',
'md:!max-w-7xl',
'md:!max-w-full',
'md:!max-w-screen',
'md:!max-w-tablet',
],
darkMode: 'class',
theme: {
extend: {
Expand Down