From 69c274d4668bb033b4255e5796e0176b2932404d Mon Sep 17 00:00:00 2001 From: Kobe Attias Date: Sat, 10 Oct 2026 22:39:41 -0400 Subject: [PATCH] Remove dead declarations inside live files, barrel-only re-exports and unused default exports Fifth chunk of the dead-code plan: the exports knip reports as imported nowhere, limited to declarations that are not used in their own file either (symbols that are only used in-file keep their export; that pass is optional and left out). - 77 declarations deleted outright: the nine unused bounty helpers, BountyHubSelector (its Hub type moves next to useBounties, its only reader), the four BaseFeedItem sections, LabelBadge, the two collapsible wrappers, useNote/useMakeNotePrivate/useUpdateNotePermissions, useUpdateOrgDetails/useUpdateOrgCoverImage, createFeedEntryFromWork, transformHub/transformContent, getAuditContentPreview and the text helpers only it called, the formatCombinedBalance pair, isValidPassword/isValidName, toNumberOrNull, isPinchGesture, ALL_CONTENT_TYPES, the deprecated HotScoreTooltip alias, the grant template object only getInitialContent returned, and the one-line Transformed* / response type aliases nothing references. - 43 barrel re-export lines dropped (Activity, Embed, Editor extensions, Table, WorkHeader, PublishingForm, AuthorPosts, BaseFeedItem and a few single type re-exports); everything behind them is still imported directly except TopicAndJournalBadge, FUNDING_KIND_LABEL and the FundingRequest interface, which had no other reader and go too. - 38 trailing `export default X;` lines removed from files that also export X by name; nothing imported the default. - @tiptap/extension-bullet-list, -ordered-list and -paragraph leave package.json: the barrel re-export was their only reference and StarterKit already provides those nodes. - Imports and local types orphaned by the deletions are removed; pre-existing unused imports in untouched lines are left alone. Co-Authored-By: Claude Fable 5.1 --- app/expert-finder/lib/generatedEmailStatus.ts | 4 - app/expert-finder/library/new/schema.ts | 1 - app/layouts/MobileBottomNav.tsx | 2 - .../components/LeaderboardContent.tsx | 2 - app/pool/doublezero/link.ts | 8 - components/Activity/index.ts | 6 - components/Auth/AuthContent.tsx | 2 - components/Bounty/lib/bountyUtil.ts | 272 ------------------ components/Comment/components/AuthorPosts.tsx | 6 - components/Comment/lib/types.ts | 20 -- components/Earn/BountyHubSelector.tsx | 230 --------------- .../components/menus/LinkMenu/LinkMenu.tsx | 2 - .../BlockquoteFigure/BlockquoteFigure.ts | 2 - .../BlockquoteFigure/Quote/Quote.ts | 2 - .../QuoteCaption/QuoteCaption.ts | 2 - .../Editor/extensions/Document/Document.ts | 2 - .../extensions/EmojiSuggestion/suggestion.ts | 2 - .../extensions/Figcaption/Figcaption.ts | 2 - components/Editor/extensions/Figure/Figure.ts | 2 - .../Editor/extensions/FontSize/FontSize.ts | 2 - .../Editor/extensions/Heading/Heading.ts | 2 - .../HorizontalRule/HorizontalRule.ts | 2 - components/Editor/extensions/Image/Image.ts | 2 - .../extensions/ImageBlock/ImageBlock.ts | 2 - .../ImageBlock/components/ImageBlockView.tsx | 2 - .../extensions/ImageUpload/ImageUpload.ts | 2 - .../ImageUpload/view/ImageUpload.tsx | 2 - .../ImageUpload/view/ImageUploader.tsx | 2 - components/Editor/extensions/Link/Link.ts | 2 - .../Editor/extensions/MultiColumn/Column.ts | 2 - .../Editor/extensions/MultiColumn/Columns.ts | 2 - .../Editor/extensions/Selection/Selection.ts | 2 - .../extensions/SlashCommand/MenuList.tsx | 2 - .../extensions/SlashCommand/SlashCommand.ts | 2 - components/Editor/extensions/Table/Header.ts | 2 - components/Editor/extensions/Table/Row.ts | 2 - components/Editor/extensions/Table/Table.ts | 2 - .../Table/menus/TableColumn/utils.ts | 2 - .../extensions/Table/menus/TableRow/utils.ts | 2 - components/Editor/extensions/Table/utils.ts | 49 ---- components/Editor/extensions/extension-kit.ts | 2 - components/Editor/extensions/index.ts | 11 +- components/Editor/lib/api.ts | 2 - components/Editor/lib/data/grantTemplate.tsx | 1 - components/Editor/lib/data/initialContent.tsx | 179 ------------ .../Editor/lib/data/proposalTemplate.tsx | 1 - components/Editor/lib/utils/cssVar.ts | 2 - .../Editor/lib/utils/getRenderContainer.ts | 2 - components/Editor/lib/utils/index.ts | 4 - .../Editor/lib/utils/isCustomNodeSelected.ts | 2 - components/Editor/lib/utils/isTextSelected.ts | 2 - components/Embed/index.ts | 3 - components/Feed/BaseFeedItem.tsx | 118 +------- components/Feed/skeletons/shared.tsx | 2 - .../dashboard/hooks/useMyFundingDocuments.ts | 3 +- components/Funding/fundingKind.ts | 5 - components/Globe/AnimatedGlobe/landDots.ts | 1 - components/Moderators/utils/auditUtils.ts | 98 ------- components/Notebook/PublishingForm/index.tsx | 1 - components/tooltips/HotScoreTooltip.tsx | 3 - components/ui/CollapsibleSection.tsx | 63 +--- components/ui/ContributorsButton.tsx | 109 ------- components/ui/LoadErrorState.tsx | 2 - components/ui/Popover.tsx | 2 - components/ui/SearchEmpty.tsx | 2 - components/ui/SwipeableDrawer.tsx | 2 - components/ui/Table/index.ts | 8 +- components/ui/TopicAndJournalBadge.tsx | 55 ---- components/work/WorkHeader/index.ts | 2 +- contexts/AuthModalContext.tsx | 16 -- hooks/useBounties.ts | 8 +- hooks/useNote.ts | 149 +--------- hooks/useOrganization.ts | 86 ------ hooks/usePinchZoom.ts | 7 - lib/utils.ts | 5 - package-lock.json | 3 - package.json | 3 - services/author.service.ts | 5 - services/content-moderation.service.ts | 2 - services/hub.service.ts | 12 - services/reaction.service.ts | 5 - services/types/api.ts | 5 - services/types/notification.dto.ts | 1 - types/authorProfile.ts | 4 +- types/comment.ts | 7 - types/feed.ts | 103 +------ types/leaderboard.ts | 3 - types/note.ts | 18 +- types/notification.ts | 2 - types/search.ts | 14 +- types/work.ts | 15 - utils/number.ts | 51 ---- utils/preprintUtil.ts | 3 - utils/url.ts | 9 - utils/validation.ts | 8 - 95 files changed, 22 insertions(+), 1867 deletions(-) delete mode 100644 components/Earn/BountyHubSelector.tsx delete mode 100644 components/ui/TopicAndJournalBadge.tsx diff --git a/app/expert-finder/lib/generatedEmailStatus.ts b/app/expert-finder/lib/generatedEmailStatus.ts index 97551beef..2e8b6c0f4 100644 --- a/app/expert-finder/lib/generatedEmailStatus.ts +++ b/app/expert-finder/lib/generatedEmailStatus.ts @@ -14,10 +14,6 @@ export function isGeneratedEmailBounced(status: string): boolean { return status === 'bounced'; } -export function isGeneratedEmailFailed(status: string): boolean { - return status === 'failed' || status === 'send_failed'; -} - export type GeneratedEmailStatusBadgeVariant = | 'default' | 'primary' diff --git a/app/expert-finder/library/new/schema.ts b/app/expert-finder/library/new/schema.ts index 921bb4f52..e934f202b 100644 --- a/app/expert-finder/library/new/schema.ts +++ b/app/expert-finder/library/new/schema.ts @@ -9,7 +9,6 @@ import { const INPUT_TYPES: InputType[] = ['abstract', 'pdf', 'full_content']; export const EXPERT_COUNT_OPTIONS = [5, 10, 25, 50, 100] as const; -export type ExpertCountOption = (typeof EXPERT_COUNT_OPTIONS)[number]; export const DEFAULT_STATE = 'All States'; diff --git a/app/layouts/MobileBottomNav.tsx b/app/layouts/MobileBottomNav.tsx index fe812540d..8b9758be1 100644 --- a/app/layouts/MobileBottomNav.tsx +++ b/app/layouts/MobileBottomNav.tsx @@ -188,5 +188,3 @@ export const MobileBottomNav: React.FC = () => { ); }; - -export default MobileBottomNav; diff --git a/app/leaderboard/components/LeaderboardContent.tsx b/app/leaderboard/components/LeaderboardContent.tsx index 2a87aa685..59a897416 100644 --- a/app/leaderboard/components/LeaderboardContent.tsx +++ b/app/leaderboard/components/LeaderboardContent.tsx @@ -12,8 +12,6 @@ import { useCurrentUserLeaderboard } from '@/hooks/useLeaderboard'; import { TopPeerReviewers } from './TopPeerReviewers'; import { TopFunders } from './TopFunders'; -export type { LeaderboardPeriod } from '@/services/leaderboard.service'; - const PERIOD_OPTIONS: { value: LeaderboardPeriod; label: string }[] = [ { value: '7_days', label: 'Last 7 Days' }, { value: '30_days', label: 'Last 30 Days' }, diff --git a/app/pool/doublezero/link.ts b/app/pool/doublezero/link.ts index 1b9a0940f..c15b27483 100644 --- a/app/pool/doublezero/link.ts +++ b/app/pool/doublezero/link.ts @@ -49,12 +49,6 @@ export const SURFACE_KM = EARTH_RADIUS_KM * CENTRAL_ANGLE; /** Straight-line distance between the endpoints, through the planet. */ export const CHORD_KM = 2 * EARTH_RADIUS_KM * Math.sin(CENTRAL_ANGLE / 2); -/** How far below the surface that chord dips at its midpoint. */ -export const CHORD_DEPTH_KM = EARTH_RADIUS_KM * (1 - Math.cos(CENTRAL_ANGLE / 2)); - -/** Closest the chord comes to the Earth's centre. Under 80 km, so: inner core. */ -export const CHORD_CENTRE_MISS_KM = EARTH_RADIUS_KM * Math.cos(CENTRAL_ANGLE / 2); - /** * A neutrino crosses the chord at c. It has mass, so strictly it is a hair * slower — but at the TeV energies this RFP contemplates the deficit is @@ -66,8 +60,6 @@ export const CHORD_MS = (CHORD_KM / C_KM_S) * 1000; /** Fiber pays twice — the longer path and the slower medium. */ export const SURFACE_MS = ((SURFACE_KM * FIBER_INDEX) / C_KM_S) * 1000; -export const SAVED_MS = SURFACE_MS - CHORD_MS; - /** * Shell boundaries by depth, as fractions of the radius, for the cross-section. * Upper/lower mantle at 660 km, the core-mantle boundary at 2,890 km, and the diff --git a/components/Activity/index.ts b/components/Activity/index.ts index 06b6c38fd..b334a7640 100644 --- a/components/Activity/index.ts +++ b/components/Activity/index.ts @@ -1,10 +1,6 @@ // Cards export { ActivityCard } from './cards/ActivityCard'; -export { ActivityCardCompact } from './cards/ActivityCardCompact'; -export { ActivityCardHeader } from './cards/ActivityCardHeader'; export { ActivityCardSkeleton } from './cards/ActivityCardSkeleton'; -export { ActivityCommentGroupCard } from './cards/ActivityCommentGroupCard'; -export { ActivityFundingGroupCard } from './cards/ActivityFundingGroupCard'; // Work card primitives, shared with the funding feeds export { ActivityTimestamp } from './cards/ActivityTimestamp'; @@ -16,9 +12,7 @@ export { WorkPreviewCard } from './work/WorkPreviewCard'; export { ActivityFeedList } from './ActivityFeedList'; export { ActivityRow } from './ActivityRow'; export { ActivityPageContent } from './ActivityPageContent'; -export { ActivityCacheBypassControl } from './ActivityCacheBypassControl'; // Sidebar -export { ActivitySidebar } from './sidebar/ActivitySidebar'; export { ActivitySidebarServer } from './sidebar/ActivitySidebarServer'; export { ActivitySidebarSkeleton } from './sidebar/ActivitySidebarSkeleton'; diff --git a/components/Auth/AuthContent.tsx b/components/Auth/AuthContent.tsx index e2fcb3a0b..14a3812d6 100644 --- a/components/Auth/AuthContent.tsx +++ b/components/Auth/AuthContent.tsx @@ -7,8 +7,6 @@ import ForgotPassword from './screens/ForgotPassword'; import { AuthScreen, type AuthAppearance, type CatalystSurface } from './types'; import AnalyticsService, { LogEvent } from '@/services/analytics.service'; -export type { AuthAppearance, CatalystSurface } from './types'; - interface AuthContentProps { onClose?: () => void; onSuccess?: () => void; diff --git a/components/Bounty/lib/bountyUtil.ts b/components/Bounty/lib/bountyUtil.ts index 7e4b6f399..b96866768 100644 --- a/components/Bounty/lib/bountyUtil.ts +++ b/components/Bounty/lib/bountyUtil.ts @@ -72,15 +72,6 @@ export const countClosedBounties = (bounties: Bounty[]): number => { return getClosedBounties(bounties).length; }; -/** - * Calculates the total amount of all bounties in an array - * @param bounties Array of bounties to sum - * @returns Total amount as a number - */ -export const calculateTotalBountyAmount = (bounties: Bounty[]): number => { - return bounties.reduce((sum, bounty) => sum + parseFloat(bounty.amount), 0); -}; - /** * Finds the first active (open) bounty in an array * @param bounties Array of bounties to search @@ -113,22 +104,6 @@ export const findClosedBounty = (bounties: Bounty[]): Bounty | undefined => { return closedBounty; }; -/** - * Gets the bounty to display from an array of bounties - * @param bounties Array of bounties - * @returns The display bounty or undefined if none found - */ -export const getDisplayBounty = (bounties: Bounty[]): Bounty | undefined => { - if (!bounties || !Array.isArray(bounties) || bounties.length === 0) { - return undefined; - } - - const activeBounty = findActiveBounty(bounties); - const closedBounty = findClosedBounty(bounties); - - return activeBounty || closedBounty; -}; - /** * Checks if a bounty is expiring soon (within 3 days) * @param expirationDate The expiration date string @@ -151,22 +126,6 @@ export const isExpiringSoon = (expirationDate?: string, daysThreshold: number = return diffDays > 0 && diffDays < daysThreshold; }; -/** - * Gets the appropriate title for a bounty based on its type and status - * @param bounty The bounty object - * @param isOpen Whether the bounty is open - * @returns The appropriate title string - */ -export const getBountyTitle = (bounty?: Bounty, isOpen?: boolean): string => { - if (bounty?.bountyType === 'REVIEW') { - return isOpen ? 'Bounty: Peer Review' : 'Awarded Bounty: Peer Review'; - } - if (bounty?.bountyType === 'ANSWER') { - return isOpen ? 'Bounty: Answer to Question' : 'Awarded Bounty: Answer to Question'; - } - return isOpen ? 'Bounty' : 'Awarded Bounty'; -}; - /** * Calculates the total awarded amount for a bounty with solutions * @param bounty The bounty object with solutions @@ -194,157 +153,6 @@ export interface Contributor { isCreator?: boolean; } -/** - * Formats contributor names for display - * @param contributors Array of contributors - * @returns Formatted string of contributor names - */ -export const formatContributorNames = (contributors: Contributor[]): string => { - if (contributors.length === 0) return 'Unknown'; - - // Find the creator first (the main bounty creator) - const creator = contributors.find((c) => c.isCreator); - const creatorName = creator?.profile.fullName || contributors[0]?.profile.fullName || 'Unknown'; - - // Get all non-creator contributors - const otherContributors = contributors.filter( - (c) => c.profile.fullName !== creatorName && c.profile.fullName - ); - - if (contributors.length === 1) { - // Case 1: Just one contributor - return creatorName; - } else if (contributors.length === 2) { - // Case 2: Two contributors - const otherContributor = otherContributors[0]; - return `${creatorName} and ${otherContributor?.profile.fullName || 'another contributor'}`; - } else { - // Case 3: More than two contributors - if (otherContributors.length === 0) { - return `${creatorName} and others`; - } else if (otherContributors.length === 1) { - return `${creatorName} and ${otherContributors[0].profile.fullName}`; - } else { - return `${creatorName}, ${otherContributors[0].profile.fullName} and ${ - otherContributors.length > 1 ? `${otherContributors.length - 1} others` : 'another' - }`; - } - } -}; - -/** - * Extracts all contributors from an array of bounties - * @param bounties Array of bounties - * @param displayBounty The main bounty being displayed - * @returns Array of contributors with profile information - */ -export const extractContributors = (bounties: Bounty[], displayBounty?: Bounty): Contributor[] => { - return bounties - .filter((bounty) => bounty.createdBy.authorProfile) - .map((bounty) => { - // A bounty is not a contribution if it doesn't have a parent bounty - const isContribution = bounty.raw && !!bounty.raw.parent; - - return { - profile: bounty.createdBy.authorProfile!, - amount: Number(bounty.amount), - isCreator: bounty.id === displayBounty?.id && !isContribution, - }; - }); -}; - -/** - * Filters out the creator from the contributors list - * @param contributors Array of contributors - * @returns Array of contributors without the creator - */ -export const filterOutCreator = (contributors: Contributor[]): Contributor[] => { - return contributors.filter((contributor) => !contributor.isCreator); -}; - -/** - * Extracts contributors for display in the UI based on the following rules: - * 1. If there are no contributors (other than the creator), return an empty array - * 2. If there are one or more contributors, return both the creator and the contributors - * - * @param bounty The bounty to extract contributors from - * @returns Array of contributors for display - */ -export const extractContributorsForDisplay = (bounty: Bounty): Contributor[] => { - // If there are no contributions, return an empty array - if (!bounty.contributions || bounty.contributions.length === 0) { - return []; - } - - // Process contributions directly from the bounty.contributions array - const contributorsFromContributions = bounty.contributions.map((contribution) => { - // Get the name from the contribution's createdBy - let fullName = 'Unknown'; - let profileImage: string | undefined = undefined; - - // Try to get profile info from authorProfile if it exists - if (contribution.createdBy.authorProfile) { - fullName = contribution.createdBy.authorProfile.fullName || 'Unknown'; - profileImage = contribution.createdBy.authorProfile.profileImage; - } - // Fallback to raw data if available - else if (contribution.raw?.created_by?.author_profile) { - const rawProfile = contribution.raw.created_by.author_profile; - fullName = `${rawProfile.first_name || ''} ${rawProfile.last_name || ''}`.trim() || 'Unknown'; - profileImage = rawProfile.profile_image; - } - - return { - profile: { - fullName, - profileImage, - isClaimed: true, - }, - amount: Number(contribution.amount), - isCreator: false, - }; - }); - - // Add the creator as a contributor if not already included - let creatorFullName = 'Unknown'; - let creatorProfileImage: string | undefined = undefined; - - // Try to get creator profile info from authorProfile if it exists - if (bounty.createdBy.authorProfile) { - creatorFullName = bounty.createdBy.authorProfile.fullName || 'Unknown'; - creatorProfileImage = bounty.createdBy.authorProfile.profileImage; - } - // Fallback to raw data if available - else if (bounty.raw?.created_by?.author_profile) { - const rawProfile = bounty.raw.created_by.author_profile; - creatorFullName = - `${rawProfile.first_name || ''} ${rawProfile.last_name || ''}`.trim() || 'Unknown'; - creatorProfileImage = rawProfile.profile_image; - } - - const creator: Contributor = { - profile: { - fullName: creatorFullName, - profileImage: creatorProfileImage, - isClaimed: true, - }, - amount: Number(bounty.amount), - isCreator: true, - }; - - // Check if the creator is already in the contributors list - const creatorAlreadyIncluded = contributorsFromContributions.some( - (c) => c.profile.fullName === creator.profile.fullName - ); - - // Return all contributors including the creator (if not already included) - const result = creatorAlreadyIncluded - ? contributorsFromContributions - : [...contributorsFromContributions, creator]; - - return result; -}; - /** * Checks if a comment has any bounties * @param comment The comment object to check @@ -362,77 +170,6 @@ export interface BountyAvatar { authorId?: number; } -/** - * Extracts avatar information from bounties for use in UI components - * @param bounties Array of bounties to extract avatars from - * @param openOnly Whether to only include open bounties (default: true) - * @returns Array of avatar objects ready for display - */ -export const extractBountyAvatars = ( - bounties: Bounty[], - openOnly: boolean = true -): BountyAvatar[] => { - // Filter bounties if openOnly is true - const filteredBounties = openOnly - ? bounties.filter((bounty) => bounty.status === 'OPEN') - : bounties; - - // Extract avatars from each bounty - const avatars = filteredBounties.flatMap((bounty) => { - const result = []; - - // Add creator avatar if available - if (bounty.createdBy) { - // Try to access profile information from raw data - const creatorRaw = bounty.raw?.created_by || {}; - const authorProfile = bounty.createdBy.authorProfile || {}; - - // Use explicit type casting to avoid TypeScript errors with optional fields - const profileImage = (authorProfile as any).profileImage || (creatorRaw as any).profile_image; - const fullName = (authorProfile as any).fullName || (creatorRaw as any).full_name; - const id = (authorProfile as any).id || (creatorRaw as any).id; - - result.push({ - src: profileImage || '/images/default-avatar.png', - alt: fullName || 'Creator', - tooltip: fullName, - authorId: id, - }); - } - - // Add contributor avatars if available - if (bounty.contributions && bounty.contributions.length > 0) { - const contributorAvatars = bounty.contributions.map((contribution) => { - const contributorRaw = contribution.raw?.user?.author_profile || {}; - const authorProfile = contribution.createdBy?.authorProfile || {}; - - // Use explicit type casting to avoid TypeScript errors with optional fields - const profileImage = - (authorProfile as any).profileImage || (contributorRaw as any).profile_image; - const fullName = (authorProfile as any).fullName || (contributorRaw as any).full_name; - const id = (authorProfile as any).id || (contributorRaw as any).id; - - return { - src: profileImage || '/images/default-avatar.png', - alt: fullName || 'Contributor', - tooltip: fullName, - authorId: id, - }; - }); - - result.push(...contributorAvatars); - } - - return result; - }); - - // Remove duplicates by authorId - return avatars.filter( - (avatar, index, self) => - avatar.authorId && index === self.findIndex((a) => a.authorId === avatar.authorId) - ); -}; - /** * Hacky shim to ensure we are rendering $150 USD for Foundation bounties. * Extracts the user ID of the bounty creator from various possible data structures. @@ -549,15 +286,6 @@ export const calculateBountyDisplayAmountUSD = (bounty: Bounty, exchangeRate: nu return Math.round(rscAmount * exchangeRate); }; -export const calculateTotalBountyDisplayAmountUSD = ( - bounties: Bounty[], - exchangeRate: number -): number => { - return bounties.reduce((total, bounty) => { - return total + calculateBountyDisplayAmountUSD(bounty, exchangeRate); - }, 0); -}; - /** * Calculates the display amount for a bounty, returning either USD or RSC based on preference. * For Foundation bounties displayed in USD, returns a flat $150. diff --git a/components/Comment/components/AuthorPosts.tsx b/components/Comment/components/AuthorPosts.tsx index c84e039f2..cf29a0d4f 100644 --- a/components/Comment/components/AuthorPosts.tsx +++ b/components/Comment/components/AuthorPosts.tsx @@ -11,10 +11,6 @@ import { AuthorPostsCarousel } from './AuthorPostsCarousel'; import { PostComposerModal } from './PostComposerModal'; import { useAuthorPostComposer } from './useAuthorPostComposer'; -export { useAuthorPostComposer } from './useAuthorPostComposer'; -export { PostComposerModal } from './PostComposerModal'; -export type { AuthorPostComposer } from './useAuthorPostComposer'; - const AuthorPostsEmptyShell: FC<{ children: ReactNode }> = ({ children }) => (

{children}

@@ -33,8 +29,6 @@ const ViewerEmptyState: FC = () => ( ); -export const ViewerPostsEmptyState = ViewerEmptyState; - interface AuthorPostsProps { posts: Comment[]; documentId: number; diff --git a/components/Comment/lib/types.ts b/components/Comment/lib/types.ts index bb84036c1..7de888ba9 100644 --- a/components/Comment/lib/types.ts +++ b/components/Comment/lib/types.ts @@ -1,5 +1,3 @@ -import { Comment } from '@/types/comment'; - export interface MentionItem { id: string | null; entityType: 'user' | 'author' | 'paper' | 'post'; @@ -78,21 +76,3 @@ export type CommentContent = | { content: Array } | { type: string; content: any[] } | string; - -/** - * Options for comment editor - */ -export interface CommentEditorOptions { - placeholder?: string; - isReadOnly?: boolean; - debug?: boolean; -} - -/** - * Result of a comment operation - */ -export interface CommentOperationResult { - success: boolean; - comment?: Comment; - error?: string; -} diff --git a/components/Earn/BountyHubSelector.tsx b/components/Earn/BountyHubSelector.tsx deleted file mode 100644 index 3ef3e1cbf..000000000 --- a/components/Earn/BountyHubSelector.tsx +++ /dev/null @@ -1,230 +0,0 @@ -'use client'; - -import { useEffect, useState, useCallback, useRef } from 'react'; -import { - MultiSelectOption, - SearchableMultiSelect, -} from '@/components/ui/form/SearchableMultiSelect'; -import { Badge } from '@/components/ui/Badge'; -import { Button } from '@/components/ui/Button'; -import { X, ChevronDown, Filter } from 'lucide-react'; -import { BaseMenu } from '@/components/ui/form/BaseMenu'; -import { BountyService } from '@/services/bounty.service'; -import { Topic } from '@/types/topic'; - -export interface Hub { - id: string | number; - name: string; - description?: string; - color?: string; -} - -interface BountyHubSelectorProps { - selectedHubs: Hub[]; - onChange: (hubs: Hub[]) => void; - error?: string | null; - displayCountOnly?: boolean; - hideSelectedItems?: boolean; -} - -export function BountyHubSelector({ - selectedHubs, - onChange, - error, - displayCountOnly = false, - hideSelectedItems = false, -}: BountyHubSelectorProps) { - const [allHubs, setAllHubs] = useState([]); - const [menuOpen, setMenuOpen] = useState(false); - const menuContentRef = useRef(null); - - // Auto-focus input when menu opens - useEffect(() => { - if (menuOpen) { - // Longer delay to ensure input is rendered and accessible - setTimeout(() => { - // Try to find the input in the document - this works even with portals - const input = document.querySelector('.SearchableMultiSelect-input'); - if (input instanceof HTMLInputElement) { - input.focus(); - } else { - // Fallback to searching entire document for any input in our component - const inputs = document.querySelectorAll('input'); - inputs.forEach((input) => { - // Find inputs that are descendants of elements with our component classes - if (input.closest('.overflow-visible.shadow-lg')) { - input.focus(); - } - }); - } - }, 150); - } - }, [menuOpen]); - - // fetch all hubs at mount - useEffect(() => { - (async () => { - const hubs = await BountyService.getBountyHubs(); - setAllHubs(hubs); - })(); - }, []); - - // Populate missing names in selectedHubs when hubs are fetched - // This is needed when we restore topics from URL params (only IDs are stored) - useEffect(() => { - if (allHubs.length === 0) return; - - const hasMissingNames = selectedHubs.some((hub) => !hub.name || hub.name === ''); - if (hasMissingNames) { - const populatedHubs = selectedHubs.map((hub) => { - // If hub already has a name, keep it - if (hub.name && hub.name !== '') { - return hub; - } - // Otherwise, find matching hub from fetched hubs and populate name/description - const matchedHub = allHubs.find((h) => h.id === hub.id); - if (matchedHub) { - return { - ...hub, - name: matchedHub.name, - description: matchedHub.description, - }; - } - // If no match found, return as-is - return hub; - }); - - const anyChanged = populatedHubs.some((hub, index) => hub.name !== selectedHubs[index]?.name); - if (anyChanged) { - onChange(populatedHubs); - } - } - }, [allHubs, selectedHubs, onChange]); - - // utility conversions - const hubsToOptions = (hubs: Hub[]): MultiSelectOption[] => - hubs.map((hub) => ({ value: String(hub.id), label: hub.name })); - - const topicsToHubs = (topics: Topic[]): Hub[] => - topics.map((topic) => ({ - id: topic.id, - name: topic.name, - description: topic.description, - })); - - const optionsToHubs = (options: MultiSelectOption[]): Hub[] => - options.map( - (opt) => - selectedHubs.find((h) => String(h.id) === opt.value) || { - id: opt.value, - name: opt.label, - } - ); - - const allHubOptions = hubsToOptions(topicsToHubs(allHubs)); - - // Local search within allHubs - const filterHubs = useCallback( - async (query: string): Promise => { - if (!query) { - return hubsToOptions(topicsToHubs(allHubs)); - } - const lowered = query.toLowerCase(); - const filtered = allHubs.filter((hub) => hub.name.toLowerCase().includes(lowered)); - return hubsToOptions(topicsToHubs(filtered)); - }, - [allHubs, selectedHubs] - ); - - const handleChange = (options: MultiSelectOption[]) => { - onChange(optionsToHubs(options)); - if (displayCountOnly) { - setMenuOpen(false); - } - }; - - const CustomSelectedItems = () => ( -
- {selectedHubs.map((hub) => ( - { - e.preventDefault(); - e.stopPropagation(); - onChange(selectedHubs.filter((h) => h.id !== hub.id)); - if (displayCountOnly) { - setMenuOpen(false); - } - }} - > - {hub.color && ( -
- )} - {hub.name} - - ))} -
- ); - - if (displayCountOnly) { - const trigger = ( - - ); - - return ( - -
- -
-
- ); - } - - return ( -
-
- - {selectedHubs.length > 0 && !hideSelectedItems && } -
-
- ); -} diff --git a/components/Editor/components/menus/LinkMenu/LinkMenu.tsx b/components/Editor/components/menus/LinkMenu/LinkMenu.tsx index 025a72e68..4b771588a 100644 --- a/components/Editor/components/menus/LinkMenu/LinkMenu.tsx +++ b/components/Editor/components/menus/LinkMenu/LinkMenu.tsx @@ -78,5 +78,3 @@ export const LinkMenu = ({ editor, appendTo }: MenuProps): React.JSX.Element => ); }; - -export default LinkMenu; diff --git a/components/Editor/extensions/BlockquoteFigure/BlockquoteFigure.ts b/components/Editor/extensions/BlockquoteFigure/BlockquoteFigure.ts index 44baaafce..0472d80b1 100644 --- a/components/Editor/extensions/BlockquoteFigure/BlockquoteFigure.ts +++ b/components/Editor/extensions/BlockquoteFigure/BlockquoteFigure.ts @@ -76,5 +76,3 @@ export const BlockquoteFigure = Figure.extend({ }; }, }); - -export default BlockquoteFigure; diff --git a/components/Editor/extensions/BlockquoteFigure/Quote/Quote.ts b/components/Editor/extensions/BlockquoteFigure/Quote/Quote.ts index 80888bbb8..9a9c0e8f5 100644 --- a/components/Editor/extensions/BlockquoteFigure/Quote/Quote.ts +++ b/components/Editor/extensions/BlockquoteFigure/Quote/Quote.ts @@ -27,5 +27,3 @@ export const Quote = Node.create({ }; }, }); - -export default Quote; diff --git a/components/Editor/extensions/BlockquoteFigure/QuoteCaption/QuoteCaption.ts b/components/Editor/extensions/BlockquoteFigure/QuoteCaption/QuoteCaption.ts index c4e51c06a..9b08e16a3 100644 --- a/components/Editor/extensions/BlockquoteFigure/QuoteCaption/QuoteCaption.ts +++ b/components/Editor/extensions/BlockquoteFigure/QuoteCaption/QuoteCaption.ts @@ -50,5 +50,3 @@ export const QuoteCaption = Node.create({ }; }, }); - -export default QuoteCaption; diff --git a/components/Editor/extensions/Document/Document.ts b/components/Editor/extensions/Document/Document.ts index 58d8edcbb..86096cd40 100644 --- a/components/Editor/extensions/Document/Document.ts +++ b/components/Editor/extensions/Document/Document.ts @@ -3,5 +3,3 @@ import { Document as TiptapDocument } from '@tiptap/extension-document'; export const Document = TiptapDocument.extend({ content: '(block|columns)+', }); - -export default Document; diff --git a/components/Editor/extensions/EmojiSuggestion/suggestion.ts b/components/Editor/extensions/EmojiSuggestion/suggestion.ts index 45db9388c..648934e1c 100644 --- a/components/Editor/extensions/EmojiSuggestion/suggestion.ts +++ b/components/Editor/extensions/EmojiSuggestion/suggestion.ts @@ -78,5 +78,3 @@ export const emojiSuggestion = { }; }, }; - -export default emojiSuggestion; diff --git a/components/Editor/extensions/Figcaption/Figcaption.ts b/components/Editor/extensions/Figcaption/Figcaption.ts index f909023d7..53cf3f208 100644 --- a/components/Editor/extensions/Figcaption/Figcaption.ts +++ b/components/Editor/extensions/Figcaption/Figcaption.ts @@ -86,5 +86,3 @@ export const Figcaption = Node.create({ return ['figcaption', mergeAttributes(HTMLAttributes), 0]; }, }); - -export default Figcaption; diff --git a/components/Editor/extensions/Figure/Figure.ts b/components/Editor/extensions/Figure/Figure.ts index ddeeeed67..f5c7e8e24 100644 --- a/components/Editor/extensions/Figure/Figure.ts +++ b/components/Editor/extensions/Figure/Figure.ts @@ -58,5 +58,3 @@ export const Figure = Node.create({ ]; }, }); - -export default Figure; diff --git a/components/Editor/extensions/FontSize/FontSize.ts b/components/Editor/extensions/FontSize/FontSize.ts index 20d6e8882..0c2746ed3 100644 --- a/components/Editor/extensions/FontSize/FontSize.ts +++ b/components/Editor/extensions/FontSize/FontSize.ts @@ -60,5 +60,3 @@ export const FontSize = Extension.create({ }; }, }); - -export default FontSize; diff --git a/components/Editor/extensions/Heading/Heading.ts b/components/Editor/extensions/Heading/Heading.ts index 844910fca..cad80692f 100644 --- a/components/Editor/extensions/Heading/Heading.ts +++ b/components/Editor/extensions/Heading/Heading.ts @@ -11,5 +11,3 @@ export const Heading = TiptapHeading.extend({ return [`h${level}`, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]; }, }); - -export default Heading; diff --git a/components/Editor/extensions/HorizontalRule/HorizontalRule.ts b/components/Editor/extensions/HorizontalRule/HorizontalRule.ts index 2ca7b56d4..31ff3188e 100644 --- a/components/Editor/extensions/HorizontalRule/HorizontalRule.ts +++ b/components/Editor/extensions/HorizontalRule/HorizontalRule.ts @@ -10,5 +10,3 @@ export const HorizontalRule = TiptapHorizontalRule.extend({ ]; }, }); - -export default HorizontalRule; diff --git a/components/Editor/extensions/Image/Image.ts b/components/Editor/extensions/Image/Image.ts index 18177fe5c..2924fea6d 100644 --- a/components/Editor/extensions/Image/Image.ts +++ b/components/Editor/extensions/Image/Image.ts @@ -3,5 +3,3 @@ import { Image as BaseImage } from '@tiptap/extension-image'; export const Image = BaseImage.extend({ group: 'block', }); - -export default Image; diff --git a/components/Editor/extensions/ImageBlock/ImageBlock.ts b/components/Editor/extensions/ImageBlock/ImageBlock.ts index c1207681d..d55aed42e 100644 --- a/components/Editor/extensions/ImageBlock/ImageBlock.ts +++ b/components/Editor/extensions/ImageBlock/ImageBlock.ts @@ -104,5 +104,3 @@ export const ImageBlock = Image.extend({ return ReactNodeViewRenderer(ImageBlockView); }, }); - -export default ImageBlock; diff --git a/components/Editor/extensions/ImageBlock/components/ImageBlockView.tsx b/components/Editor/extensions/ImageBlock/components/ImageBlockView.tsx index f9d508407..f12e05557 100644 --- a/components/Editor/extensions/ImageBlock/components/ImageBlockView.tsx +++ b/components/Editor/extensions/ImageBlock/components/ImageBlockView.tsx @@ -43,5 +43,3 @@ export const ImageBlockView = (props: ImageBlockViewProps) => { ); }; - -export default ImageBlockView; diff --git a/components/Editor/extensions/ImageUpload/ImageUpload.ts b/components/Editor/extensions/ImageUpload/ImageUpload.ts index ce2ac647d..dfe7a053e 100644 --- a/components/Editor/extensions/ImageUpload/ImageUpload.ts +++ b/components/Editor/extensions/ImageUpload/ImageUpload.ts @@ -50,5 +50,3 @@ export const ImageUpload = Node.create({ return ReactNodeViewRenderer(ImageUploadComponent); }, }); - -export default ImageUpload; diff --git a/components/Editor/extensions/ImageUpload/view/ImageUpload.tsx b/components/Editor/extensions/ImageUpload/view/ImageUpload.tsx index b475feb65..bc5770d90 100644 --- a/components/Editor/extensions/ImageUpload/view/ImageUpload.tsx +++ b/components/Editor/extensions/ImageUpload/view/ImageUpload.tsx @@ -33,5 +33,3 @@ export const ImageUpload = ({ ); }; - -export default ImageUpload; diff --git a/components/Editor/extensions/ImageUpload/view/ImageUploader.tsx b/components/Editor/extensions/ImageUpload/view/ImageUploader.tsx index e7adf2753..cf931bb5c 100644 --- a/components/Editor/extensions/ImageUpload/view/ImageUploader.tsx +++ b/components/Editor/extensions/ImageUpload/view/ImageUploader.tsx @@ -63,5 +63,3 @@ export const ImageUploader = ({ onUpload }: { onUpload: (url: string) => void })
); }; - -export default ImageUploader; diff --git a/components/Editor/extensions/Link/Link.ts b/components/Editor/extensions/Link/Link.ts index 230d10ed5..90ae2d717 100644 --- a/components/Editor/extensions/Link/Link.ts +++ b/components/Editor/extensions/Link/Link.ts @@ -88,5 +88,3 @@ export const Link = TiptapLink.extend({ ]; }, }); - -export default Link; diff --git a/components/Editor/extensions/MultiColumn/Column.ts b/components/Editor/extensions/MultiColumn/Column.ts index 592caf7a4..c6f42d813 100644 --- a/components/Editor/extensions/MultiColumn/Column.ts +++ b/components/Editor/extensions/MultiColumn/Column.ts @@ -29,5 +29,3 @@ export const Column = Node.create({ ]; }, }); - -export default Column; diff --git a/components/Editor/extensions/MultiColumn/Columns.ts b/components/Editor/extensions/MultiColumn/Columns.ts index 35699bff2..f1fa1363e 100644 --- a/components/Editor/extensions/MultiColumn/Columns.ts +++ b/components/Editor/extensions/MultiColumn/Columns.ts @@ -61,5 +61,3 @@ export const Columns = Node.create({ ]; }, }); - -export default Columns; diff --git a/components/Editor/extensions/Selection/Selection.ts b/components/Editor/extensions/Selection/Selection.ts index 38d84e720..8bcba060c 100644 --- a/components/Editor/extensions/Selection/Selection.ts +++ b/components/Editor/extensions/Selection/Selection.ts @@ -32,5 +32,3 @@ export const Selection = Extension.create({ ]; }, }); - -export default Selection; diff --git a/components/Editor/extensions/SlashCommand/MenuList.tsx b/components/Editor/extensions/SlashCommand/MenuList.tsx index 317101def..fe5432e9b 100644 --- a/components/Editor/extensions/SlashCommand/MenuList.tsx +++ b/components/Editor/extensions/SlashCommand/MenuList.tsx @@ -153,5 +153,3 @@ export const MenuList = React.forwardRef((props: MenuListProps, ref) => { }); MenuList.displayName = 'MenuList'; - -export default MenuList; diff --git a/components/Editor/extensions/SlashCommand/SlashCommand.ts b/components/Editor/extensions/SlashCommand/SlashCommand.ts index b098aebab..0a2e71752 100644 --- a/components/Editor/extensions/SlashCommand/SlashCommand.ts +++ b/components/Editor/extensions/SlashCommand/SlashCommand.ts @@ -354,5 +354,3 @@ export const SlashCommand = Extension.create({ }; }, }); - -export default SlashCommand; diff --git a/components/Editor/extensions/Table/Header.ts b/components/Editor/extensions/Table/Header.ts index cc1610d37..9fdc66bda 100644 --- a/components/Editor/extensions/Table/Header.ts +++ b/components/Editor/extensions/Table/Header.ts @@ -85,5 +85,3 @@ export const TableHeader = TiptapTableHeader.extend({ ]; }, }); - -export default TableHeader; diff --git a/components/Editor/extensions/Table/Row.ts b/components/Editor/extensions/Table/Row.ts index 4b00633c6..49ab93b98 100644 --- a/components/Editor/extensions/Table/Row.ts +++ b/components/Editor/extensions/Table/Row.ts @@ -4,5 +4,3 @@ export const TableRow = TiptapTableRow.extend({ allowGapCursor: false, content: 'tableCell*', }); - -export default TableRow; diff --git a/components/Editor/extensions/Table/Table.ts b/components/Editor/extensions/Table/Table.ts index 1ad9f9214..1bc15f525 100644 --- a/components/Editor/extensions/Table/Table.ts +++ b/components/Editor/extensions/Table/Table.ts @@ -1,5 +1,3 @@ import { Table as TiptapTable } from '@tiptap/extension-table'; export const Table = TiptapTable.configure({ resizable: true, lastColumnResizable: false }); - -export default Table; diff --git a/components/Editor/extensions/Table/menus/TableColumn/utils.ts b/components/Editor/extensions/Table/menus/TableColumn/utils.ts index a74bbf64c..4f9e3af3e 100644 --- a/components/Editor/extensions/Table/menus/TableColumn/utils.ts +++ b/components/Editor/extensions/Table/menus/TableColumn/utils.ts @@ -35,5 +35,3 @@ export const isColumnGripSelected = ({ return !!gripColumn; }; - -export default isColumnGripSelected; diff --git a/components/Editor/extensions/Table/menus/TableRow/utils.ts b/components/Editor/extensions/Table/menus/TableRow/utils.ts index 33d12b8a4..266dc1429 100644 --- a/components/Editor/extensions/Table/menus/TableRow/utils.ts +++ b/components/Editor/extensions/Table/menus/TableRow/utils.ts @@ -35,5 +35,3 @@ export const isRowGripSelected = ({ return !!gripRow; }; - -export default isRowGripSelected; diff --git a/components/Editor/extensions/Table/utils.ts b/components/Editor/extensions/Table/utils.ts index bc530d3e9..3dac6e7ba 100644 --- a/components/Editor/extensions/Table/utils.ts +++ b/components/Editor/extensions/Table/utils.ts @@ -142,29 +142,6 @@ export const getCellsInRow = (rowIndex: number | number[]) => (selection: Select return null; }; -export const getCellsInTable = (selection: Selection) => { - const table = findTable(selection); - - if (table) { - const map = TableMap.get(table.node); - const cells = map.cellsInRect({ - left: 0, - right: map.width, - top: 0, - bottom: map.height, - }); - - return cells.map((nodePos) => { - const node = table.node.nodeAt(nodePos); - const pos = nodePos + table.start; - - return { pos, start: pos + 1, node }; - }); - } - - return null; -}; - export const findParentNodeClosestToPos = ( $pos: ResolvedPos, predicate: (node: Node) => boolean @@ -185,13 +162,6 @@ export const findParentNodeClosestToPos = ( return null; }; -export const findCellClosestToPos = ($pos: ResolvedPos) => { - const predicate = (node: Node) => - node.type.spec.tableRole && /cell/i.test(node.type.spec.tableRole); - - return findParentNodeClosestToPos($pos, predicate); -}; - const select = (type: 'row' | 'column') => (index: number) => (tr: Transaction) => { const table = findTable(tr.selection); const isRowSelection = type === 'row'; @@ -237,22 +207,3 @@ const select = (type: 'row' | 'column') => (index: number) => (tr: Transaction) export const selectColumn = select('column'); export const selectRow = select('row'); - -export const selectTable = (tr: Transaction) => { - const table = findTable(tr.selection); - - if (table) { - const { map } = TableMap.get(table.node); - - if (map && map.length) { - const head = table.start + map[0]; - const anchor = table.start + map[map.length - 1]; - const $head = tr.doc.resolve(head); - const $anchor = tr.doc.resolve(anchor); - - return tr.setSelection(new CellSelection($anchor, $head)); - } - } - - return tr; -}; diff --git a/components/Editor/extensions/extension-kit.ts b/components/Editor/extensions/extension-kit.ts index 6908fa634..c5daeb0c8 100644 --- a/components/Editor/extensions/extension-kit.ts +++ b/components/Editor/extensions/extension-kit.ts @@ -174,5 +174,3 @@ export const ExtensionKit = ({ customDocument, placeholderConfig }: ExtensionKit Youtube, ReadOnlyFigure, ]; - -export default ExtensionKit; diff --git a/components/Editor/extensions/index.ts b/components/Editor/extensions/index.ts index c27a29db8..e05904b17 100644 --- a/components/Editor/extensions/index.ts +++ b/components/Editor/extensions/index.ts @@ -5,7 +5,7 @@ export { Highlight } from '@tiptap/extension-highlight'; export { CharacterCount } from '@tiptap/extension-character-count'; export { Underline } from '@tiptap/extension-underline'; export { Placeholder } from '@tiptap/extension-placeholder'; -export { Emoji, gitHubEmojis } from '@tiptap/extension-emoji'; +export { Emoji } from '@tiptap/extension-emoji'; export { TextAlign } from '@tiptap/extension-text-align'; export { TextStyle } from '@tiptap/extension-text-style'; export { FontFamily } from '@tiptap/extension-font-family'; @@ -16,14 +16,11 @@ export { Dropcursor } from '@tiptap/extension-dropcursor'; export { Subscript } from '@tiptap/extension-subscript'; export { TableOfContents } from '@tiptap/extension-table-of-contents'; export { Superscript } from '@tiptap/extension-superscript'; -export { Paragraph } from '@tiptap/extension-paragraph'; -export { BulletList } from '@tiptap/extension-bullet-list'; -export { OrderedList } from '@tiptap/extension-ordered-list'; export { TaskItem } from '@tiptap/extension-task-item'; export { TaskList } from '@tiptap/extension-task-list'; export { FileHandler } from '@tiptap/extension-file-handler'; export { Details, DetailsContent, DetailsSummary } from '@tiptap/extension-details'; -export { Mathematics, migrateMathStrings } from '@tiptap/extension-mathematics'; +export { Mathematics } from '@tiptap/extension-mathematics'; export { UniqueID } from '@tiptap/extension-unique-id'; export { Selection } from './Selection'; @@ -35,14 +32,10 @@ export { Document } from './Document'; export { TrailingNode } from './TrailingNode'; export { SlashCommand } from './SlashCommand'; export { FontSize } from './FontSize'; -export { Figure } from './Figure'; export { Figcaption } from './Figcaption'; export { BlockquoteFigure } from './BlockquoteFigure'; -export { Quote } from './BlockquoteFigure/Quote'; -export { QuoteCaption } from './BlockquoteFigure/QuoteCaption'; export { Link } from './Link'; export { ImageUpload } from './ImageUpload'; export { ImageBlock } from './ImageBlock'; export { Columns, Column } from './MultiColumn'; export { emojiSuggestion } from './EmojiSuggestion'; -export { Youtube } from './Youtube'; diff --git a/components/Editor/lib/api.ts b/components/Editor/lib/api.ts index c8ec359dc..1dd4bc9f9 100644 --- a/components/Editor/lib/api.ts +++ b/components/Editor/lib/api.ts @@ -11,5 +11,3 @@ export class API { } }; } - -export default API; diff --git a/components/Editor/lib/data/grantTemplate.tsx b/components/Editor/lib/data/grantTemplate.tsx index 93bc86d18..a18427946 100644 --- a/components/Editor/lib/data/grantTemplate.tsx +++ b/components/Editor/lib/data/grantTemplate.tsx @@ -298,5 +298,4 @@ const grantTemplate: Template = { ], }; -export type GrantTemplate = typeof grantTemplate; export default grantTemplate; diff --git a/components/Editor/lib/data/initialContent.tsx b/components/Editor/lib/data/initialContent.tsx index e110f21a5..d0ba9ef32 100644 --- a/components/Editor/lib/data/initialContent.tsx +++ b/components/Editor/lib/data/initialContent.tsx @@ -165,183 +165,4 @@ const researchTemplate: Template = { ], }; -const grantTemplate: Template = { - type: 'doc', - content: [ - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 1, - }, - content: [ - { - type: 'text', - text: 'Request for Proposal Title', - }, - ], - }, - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 2, - }, - content: [ - { - type: 'text', - text: 'Executive Summary', - }, - ], - }, - { - type: 'paragraph', - attrs: { - class: null, - textAlign: 'left', - }, - content: [ - { - type: 'text', - text: 'Brief overview of your proposal...', - }, - ], - }, - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 2, - }, - content: [ - { - type: 'text', - text: 'Project Description', - }, - ], - }, - { - type: 'paragraph', - attrs: { - class: null, - textAlign: 'left', - }, - content: [ - { - type: 'text', - text: 'Detailed description of your proposed project...', - }, - ], - }, - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 2, - }, - content: [ - { - type: 'text', - text: 'Goals and Objectives', - }, - ], - }, - { - type: 'paragraph', - attrs: { - class: null, - textAlign: 'left', - }, - content: [ - { - type: 'text', - text: 'List your project goals and objectives...', - }, - ], - }, - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 2, - }, - content: [ - { - type: 'text', - text: 'Methodology', - }, - ], - }, - { - type: 'paragraph', - attrs: { - class: null, - textAlign: 'left', - }, - content: [ - { - type: 'text', - text: 'Describe your approach and methods...', - }, - ], - }, - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 2, - }, - content: [ - { - type: 'text', - text: 'Budget', - }, - ], - }, - { - type: 'paragraph', - attrs: { - class: null, - textAlign: 'left', - }, - content: [ - { - type: 'text', - text: 'Detailed budget breakdown...', - }, - ], - }, - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 2, - }, - content: [ - { - type: 'text', - text: 'Timeline', - }, - ], - }, - { - type: 'paragraph', - attrs: { - class: null, - textAlign: 'left', - }, - content: [ - { - type: 'text', - text: 'Project timeline and milestones...', - }, - ], - }, - ], -}; - -export const getInitialContent = (type: 'research' | 'grant' = 'research'): Template => { - return type === 'research' ? researchTemplate : grantTemplate; -}; - export const initialContent = researchTemplate; // Default export for backward compatibility diff --git a/components/Editor/lib/data/proposalTemplate.tsx b/components/Editor/lib/data/proposalTemplate.tsx index 31105afb8..833c434ce 100644 --- a/components/Editor/lib/data/proposalTemplate.tsx +++ b/components/Editor/lib/data/proposalTemplate.tsx @@ -308,5 +308,4 @@ const proposalTemplate: Template = { ], }; -export type ProposalTemplate = typeof proposalTemplate; export default proposalTemplate; diff --git a/components/Editor/lib/utils/cssVar.ts b/components/Editor/lib/utils/cssVar.ts index 02ce5eed8..0bd1d98a1 100644 --- a/components/Editor/lib/utils/cssVar.ts +++ b/components/Editor/lib/utils/cssVar.ts @@ -10,5 +10,3 @@ export const cssVar = (name: string, value?: string) => { return getComputedStyle(document.body).getPropertyValue(currentName); }; - -export default cssVar; diff --git a/components/Editor/lib/utils/getRenderContainer.ts b/components/Editor/lib/utils/getRenderContainer.ts index 6fab7613e..05e19a6cf 100644 --- a/components/Editor/lib/utils/getRenderContainer.ts +++ b/components/Editor/lib/utils/getRenderContainer.ts @@ -39,5 +39,3 @@ export const getRenderContainer = (editor: Editor, nodeType: string) => { return container; }; - -export default getRenderContainer; diff --git a/components/Editor/lib/utils/index.ts b/components/Editor/lib/utils/index.ts index 1623ee7fa..2781b8712 100644 --- a/components/Editor/lib/utils/index.ts +++ b/components/Editor/lib/utils/index.ts @@ -5,8 +5,4 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function randomElement(array: Array): T { - return array[Math.floor(Math.random() * array.length)]; -} - export * from './cssVar'; diff --git a/components/Editor/lib/utils/isCustomNodeSelected.ts b/components/Editor/lib/utils/isCustomNodeSelected.ts index 68876cf49..a1f696d4e 100644 --- a/components/Editor/lib/utils/isCustomNodeSelected.ts +++ b/components/Editor/lib/utils/isCustomNodeSelected.ts @@ -43,5 +43,3 @@ export const isCustomNodeSelected = (editor: Editor, node: HTMLElement) => { return customNodes.some((type) => editor.isActive(type)) || isTableGripSelected(node); }; - -export default isCustomNodeSelected; diff --git a/components/Editor/lib/utils/isTextSelected.ts b/components/Editor/lib/utils/isTextSelected.ts index 265c51695..994700bbd 100644 --- a/components/Editor/lib/utils/isTextSelected.ts +++ b/components/Editor/lib/utils/isTextSelected.ts @@ -25,5 +25,3 @@ export const isTextSelected = ({ editor }: { editor: Editor }) => { return true; }; - -export default isTextSelected; diff --git a/components/Embed/index.ts b/components/Embed/index.ts index 98b7e87ba..0044eca27 100644 --- a/components/Embed/index.ts +++ b/components/Embed/index.ts @@ -1,6 +1,3 @@ export { Embed } from './Embed'; export { EmbedCarousel } from './EmbedCarousel'; export { InlineRichLink } from './InlineRichLink'; -export { EmbedExpandedContent, XEmbedIframe, isEmbedExpandable } from './EmbedExpandedContent'; -export { useLinkPreview } from './hooks/useLinkPreview'; -export type { EmbedSize, PreviewData } from './types'; diff --git a/components/Feed/BaseFeedItem.tsx b/components/Feed/BaseFeedItem.tsx index 67cfd3817..94c402d35 100644 --- a/components/Feed/BaseFeedItem.tsx +++ b/components/Feed/BaseFeedItem.tsx @@ -1,6 +1,6 @@ 'use client'; -import { FC, ReactNode, useState } from 'react'; +import { FC, ReactNode } from 'react'; import { FeedContentType, FeedEntry, @@ -11,11 +11,8 @@ import { FeedItemHeader } from '@/components/Feed/FeedItemHeader'; import { FeedItemActions } from '@/components/Feed/FeedItemActions'; import { CardWrapper } from './CardWrapper'; import { cn } from '@/utils/styles'; -import { stripHtml, truncateText } from '@/utils/stringUtils'; -import { TopicAndJournalBadge } from '@/components/ui/TopicAndJournalBadge'; +import { stripHtml } from '@/utils/stringUtils'; import { useNavigation } from '@/contexts/NavigationContext'; -import { Button } from '@/components/ui/Button'; -import { ChevronDown } from 'lucide-react'; import { BountyInfoSummary } from '@/components/Bounty/BountyInfoSummary'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; @@ -95,44 +92,6 @@ export interface StatusSectionProps { className?: string; } -// Subcomponents -export const BadgeSection: FC = ({ - contentType, - topics = [], - className, - onClick, -}) => { - return ( -
- {/* Content type badge would be rendered here */} - {topics.map((topic) => ( -
{ - e.stopPropagation(); - onClick?.(e); - }} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - e.stopPropagation(); - onClick?.(e as any); - } - }} - role={onClick ? 'button' : undefined} - tabIndex={onClick ? 0 : undefined} - > - -
- ))} -
- ); -}; - export const TitleSection: FC = ({ title, className, href, onClick }) => { const titleStyles = cn( 'text-md md:!text-lg font-semibold text-gray-900 mb-1 hover:underline', @@ -152,55 +111,13 @@ export const TitleSection: FC = ({ title, className, href, on return content; }; -export const ContentSection: FC = ({ - content, - className, - maxLength = 200, -}) => { - const [isExpanded, setIsExpanded] = useState(false); - const isTextTruncated = content && content.length > maxLength; - - const handleToggleExpand = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsExpanded(!isExpanded); - }; - - // Default: render truncated plain text - return ( - - ); -}; - // Re-export ImageSection for backwards compatibility -export { ImageSection, type ImageSectionProps } from './ImageSection'; +export { ImageSection } from './ImageSection'; export const MetadataSection: FC = ({ children, className }) => { return
{children}
; }; -export const CTASection: FC = ({ children, className }) => { - return
{children}
; -}; - // Primary action component interface export interface PrimaryActionSectionProps { children: ReactNode; @@ -223,35 +140,6 @@ export const PrimaryActionSection: FC = ({ children, ); }; -// Re-export FeedItemAbstractSection for backwards compatibility -export { - FeedItemAbstractSection, - type FeedItemAbstractSectionProps, -} from './FeedItemAbstractSection'; - -export const StatusSection: FC = ({ status, statusText, className }) => { - if (!status) return null; - - const statusConfig = { - open: { color: 'bg-emerald-500', textColor: 'text-emerald-700', text: 'Open' }, - closed: { color: 'bg-gray-400', textColor: 'text-gray-500', text: 'Closed' }, - expired: { color: 'bg-red-500', textColor: 'text-red-700', text: 'Expired' }, - active: { color: 'bg-blue-500', textColor: 'text-blue-700', text: 'Active' }, - inactive: { color: 'bg-gray-400', textColor: 'text-gray-500', text: 'Inactive' }, - }; - - const config = statusConfig[status] ?? statusConfig.inactive; - - return ( -
- - - {statusText ?? config.text} - -
- ); -}; - // Main BaseFeedItem component export const BaseFeedItem: FC = ({ entry, diff --git a/components/Feed/skeletons/shared.tsx b/components/Feed/skeletons/shared.tsx index 400b33406..786e97589 100644 --- a/components/Feed/skeletons/shared.tsx +++ b/components/Feed/skeletons/shared.tsx @@ -1,8 +1,6 @@ import { FC, ReactNode } from 'react'; import { cn } from '@/utils/styles'; -export const skeletonPulse = 'bg-gray-200 animate-pulse rounded'; - export const SkeletonCardShell: FC<{ children: ReactNode; className?: string }> = ({ children, className, diff --git a/components/Funding/dashboard/hooks/useMyFundingDocuments.ts b/components/Funding/dashboard/hooks/useMyFundingDocuments.ts index a0a98a4e1..3fa940301 100644 --- a/components/Funding/dashboard/hooks/useMyFundingDocuments.ts +++ b/components/Funding/dashboard/hooks/useMyFundingDocuments.ts @@ -2,12 +2,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useFeed } from '@/hooks/useFeed'; -import { FUNDING_KIND_LABEL, type FundingKind } from '@/components/Funding/fundingKind'; +import type { FundingKind } from '@/components/Funding/fundingKind'; import type { FeedEntry, FeedGrantContent, FeedPostContent } from '@/types/feed'; import { formatTimeAgo } from '@/utils/date'; import { buildWorkUrl } from '@/utils/url'; -export { FUNDING_KIND_LABEL }; export type { FundingKind }; /** An amount in both currencies, shown in whichever the user prefers. */ diff --git a/components/Funding/fundingKind.ts b/components/Funding/fundingKind.ts index 1fd2b2ceb..09bdf14c5 100644 --- a/components/Funding/fundingKind.ts +++ b/components/Funding/fundingKind.ts @@ -3,11 +3,6 @@ import { faBullhorn, faFileSignature } from '@fortawesome/pro-light-svg-icons'; export type FundingKind = 'rfp' | 'proposal'; -export const FUNDING_KIND_LABEL: Record = { - rfp: 'RFP', - proposal: 'Proposal', -}; - /** Shared document icons for publishing, thumbnails, and funding dashboards. */ export const FUNDING_KIND_ICON: Record = { rfp: faBullhorn, diff --git a/components/Globe/AnimatedGlobe/landDots.ts b/components/Globe/AnimatedGlobe/landDots.ts index fe8ba988f..5121e96e0 100644 --- a/components/Globe/AnimatedGlobe/landDots.ts +++ b/components/Globe/AnimatedGlobe/landDots.ts @@ -1,6 +1,5 @@ // AUTO-GENERATED by tools land-dot generator. Do not edit by hand. // 5776 land sample points (unit vectors) packed as signed int8 [x,y,z] // triplets, base64-encoded. Decoded at runtime in AnimatedGlobe. -export const LAND_DOTS_COUNT = 5776; export const LAND_DOTS_B64 = '94IM+YIP8IID9IIM8IIH9oIP7oIB8YIL+YIS7YMGFIP+84MP7IP/7oMK9oMS64MD8IMOBoMV84MS6YMA7YMN94MV74MR6oML84MV54MD7IQQ54T66IQIBoQZ8IQU6YQO54T29IQY5YQF7IQT5oQL6ITyG4T68IQX5IQC6YQR9YQb7IQW44T+5oUOHIX38YUa6YUU4oX6HoX77YUZ4YUA5oURHYX084UdG4UQIIX/6YUXIIX47oUb34UD5oUU9YUfIYb86oYaIIb18IYeHYYTI4YA5oYX4obuIob57IYdI4YF44YU8oYhJIb954Ya3Ib8HYbqI4b17YYfHoYV24YEJYYC44YXIIfu4YfrJYf66YcdI4fy74ciF4fhJof/5Icb2of6IIfrJYcMJof36ocg2YcDJ4cE4IcX2of1I4fvFYfeJ4f85oce2If9H4fnJ4cK24jwJojz7Ygj2IgGGYjhKIgB4YgbI4jrJogP3YjrKIj46IghHojkKYgHFIgk2YjyJojw74gm2IgKF4jeKoj+44geIojoEIjZ2ojtKYj16ogk1ogEHIjgKogD34ga14j1Jojs74jZ14gNFYjbK4j65Ygh1Yn+IYnkKokJE4kn2InwKYnx7Ikn1YkIGondLIkA4IkeBInUJYno7InZKYkPEonYK4n254kkH4nh44neLIkGConVFokn1onzKInt8YnWKIkV1YkMGIna3InlLYn84okhJInl6InaLIkMEInWEYkqK4ny6Ykn04kGHYnd4IngLYkC3YkdBonTGokl04n2KIrp7YrW1YoQFYrX2YrnLYr45IokIorh5IrbLYoIDIrTFYopK4rv8orUG4ra3YriL4r+34ohAorR0or6J4rl6YrXLYoOEorVEIos1orqLor05oonJYod0YoEIIre4IrdL4oF2oocCYrRGYoo0orzKorr7orUK4oV0ooOGIrX2YrkL4r64Yok/4rQIYoi0Ir9JYri5YrYL4oLD4rSFIosLYrwKIsaHova3YvfMIsB3IsgBYvQHYsn0Iv3KYvn6ovVLYsR04sSFYvUDosv1ovnMIv244soJYsgz4sBI4ve4YvaMIsHC4vQGIsr0YvwLYvs8IvRK4sY0IsMG4vX2YvhMYv93oskAYvPz4v7KIvj5ovW04sWEovSEosu04vqMIvyKIsdzosFIYva3YvcMosD2YsfCIvO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kccYD8dwccf7ScdXkMfx58dvbMciJsdrbsfj/8dxcccKPMdgk8fiZcfMZ8cxGMdvccfyUMdR8Mdxb8cZL8dnl8fUa8fakMcTCcdxcscBRMdbkMfrasgoIchtb8jpVshK+shyccgQOMhjlMjcaMjRZMg2Eshwcsj4TMhV68hwbsgfKshqbcjfA8hycsgHQMhfkcjlZMjJacguG8hvccjuU8hP9MhycMgWM8hmasjWYsg7DMlycsn9SMlZj8nv5clvbMklJclsb8nlWclI/clycskNO8liksngZ8nOZskzFslwcsn0T8lT7slxb8kcLslpbcncX8lAB8lyc8kDkMnpaskqH8luccnrVslM98lyccoTNsplasrTZMo4EMpyc8r6S8pX6MpwbsoiKMpsb8rhAcpzc8oJPsphkcrjZsrKGspwcsrwUspR8cpycMoYMcpobMrYCspzc8oARspbj8rsbMonI8tucMvnWMtK+8tzcssPOstkksvdacvPFMtyc8v2TstV7Mtyb8seLMtrbsveXstDBMtzc8sGQstfj8vmHctwcsvtVctP9ctzcssVNctna8vVj8sYDstzdMz8ScxZjczw5sxxbswkJsxtcMzj/8x0c8wMPcxikczhZ8zMF8xxc8zzUcxT78xzccwbL8xqbszaYcxACMx0dMwCjszqY8zDbMwqIcxvcszpV8xM+cx0c8wROM1mas3REs1zdM34Tc1X6c1ycM0hKs1scM3gXs1FAs10dM0IQM1hj83kG81xc83vVM1R8810cs0XM81pbc3WY809DM10dM3+jc3t481xbs0mJM1vcc7lWs5K/M51dM4OO85kac7OFc5zdM71UM5V7c5zcc4dLs5rb87cYM5CBs51dc4ERM5fjs7nZc7FH85xc87rV85P9s50c84UNs5nks7YbM7TEM50dc77S89Z589ycM8jKM9ucc/iXc9HAM91dc8KP89jj8/iGc9ydM/xU89T8M90cs8aMc9qbs/YY88/jM8UCs91dc8Bjc/r4c9xbs8pI89wc8/nWc9M+s91dM8QOs9mE9B0ddD3TtBX6tBzcdAgLNBtcNDeX9BFBNB1ddAHQtBhjtDlHdBydNDtVtBR9NB1dNAWNdBpbdDVZdA9jdAYDdB1jNDv5NBycNAlJtBvctDkXNFJ/tF2ddENPdFlF9F0ddHzUtFV7tF1c9Ec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KyBznyBLQyBniyAmGyB4sSBfXiCxliA+NCBvSyCepiBTSyBhjyAvJSB1XiFPVyGpmyFGPSFqiSEgrSFaYiG2kyE4LiFyoiFORiFljSEpHyF3WiFUWyGumCFBOCFtRyGcqCFWTiFfkCIzKSJ0niJJQCJoiyIjsCJdXyKzlSI7MiJwTCKgpCJRSSJijiItIyJ1XCJQWCKrmiJEOyJrqyNYYyO4kiM2LCNyoSNMQyNmjCMnHCN3syNfWCNVXCOwlyM/NSNuSSOepyNUTCNgkCMwJiN0VSOpnSRHPiRpryRbYCS1lCQ5MCRwoyRPRiRjjiQqICR1WiRSWSStmiRCOSRsRiScqiRWZCS6kiUzKiVyUiWmnyVKQSVnGiV3siVdViVWXSWylyU8MyVuSiWgpiVSSSVhkCUuJCV0VyWrnCVFPCVqrSZZYSa3lCY3LSZxoiZNRCZkjiYoHiZ2WCZTWyawmSY/NiZsRyaeqSZUZCa8kSYxJyZzVCaonidIPydnsCdbXie0lic6MSdvTCeipSdPRydijycrISd0WidQWCetmydCOSdqRCecrCdXBCeHYie5kyc0KydxUCimoShLQihlGyh1VihUXCiymCg9NChtSSigqChSZSi/kSgvJShzVSiqnihFPShoryhZXym3lSk4LilvTSmkpClNRSljjykpHyl0WClRWSmvmilANylrRSmeqylUYym8kykyKClxUimooClIQClmsipbVCpWXSq0lyo7MSptSiqipypQZirBkSosIipzViqtnSpDOippQiqcripXYCq5lSo1LCpvTyumoytLQytjHCt0VitTWiuxmis+NStrRyugqStSZCu+kiswJitxUyuqnytGPStmsStZXiy2lyw4LyxuTCykpixOZyzDkCwqICxzWSxQVyyvnCxBOCxpRCyerCxVYSy7lCwzKSxwUCyooixJQCxkVC1UWy2zmS08Mi1sSC2iqC1QZC3Aki0tIy1xVC2sni1EOy1nQC2cry1XXy24li02LS1uTS6mpS5LQy5iZy7GHi5zVy5RWC6xmy4/NS5qRS6gqy5TYi69lC4wJy5wUS6qoS5HPi5lai7LPS6bsi5ZUi5VXC62mS85MC9sSi+kpy9OZS/Dki8rIS9xVS+uni9BHC+NOS9oQi+eri9VXy+7li80Ki9uTi+opC9JQS9iaC/IVDBSWTCzmzA8MzBqRzCiqjBQYzDAlDAuJDBwUjCsoDBEPDBlajDOPjCcsTBXXTC4mDA3LTBsSzCmpjFMRDFgZTHFkjEoHzFxVzFPVjGxnTE/NjFoQzGgrTFTYDG9ljExKDFuTzGqozFHPjFjaDHKOzGbUjFTWjG1mjE6MTJqSDKkqTJOYzLClDIsIjJwUzKuoDJCOTJmajLQQDKeXjK6mDI0KzJsTDKopjJKQTJhZjLHVDNQVzOznTM9NDNoRTOirDNRYTO/ljMvJTNuUTOsojNFPDNkaDPNPTOdWzO4mjM3LjNrSTOmqDNMZDTElDQpIDRwVDSwnzRANzRmQTSgXjS9lzQyKTRsTTSqpTRHPzRhZjTKOTSbUjRRWDS1nDQ6MTRpRjWkqzVOYTXClTUsIzVuUjWuoTVCOjVkaTXPPjWfXDW6mjU1LDVrSjWopzVKZDXHlDUnNjWaHTVvVTaznjY9NDZmQzaiXza/lzYwJjZsTzaspDZFPTZiZzbMOzadWTa3nDY4LzZpRzamqjZMYjbElTcqITduUzewoTdANzdkPzehXDe8mTczKTdrTDeqpzdHZDfJNzecVje1njc7MjdnRDikXzjBlzgtJDhsUDiuozhDOjhiZzjPPDifWji6mzg2LThpSDioqThKYjjGlTgoNDibHjhuUzizoDk+NTllQTmjXTm/mTkwJzlrTTmspjlFPTlgZTnMOTmeVzm3njk4MDlnRTmmYDnElzkrITpsUTqwozpAODpiZzrRPjqhWjq8mzozKjppSjqqqTpIYjrJlTolNTqdVDq1oDo7MzplQjqlXTvBmTsuJTtrTjuupTtDOztgZTvOOjugWDu6nTs2LTtnRjuoqztKYDvGlzsoHztsUTuzojs+NjxjPzyjWzy+mzwxKDxpSzysqDxFYzzLNzyeVTy3oDw5MDxlQzynXjzDmTwrIjxqTj2xpT1BOD1gZT3QPD2iWD28nT00Kz1nRz2rqz1IYD3Ilz0mMz2dUj21oj08Mz1jQD2lWz7Bmz4uJT5pSz6vqD5DYz7NOD6gVj66nz43Lj5lRD6pXj7GmT4pTz6zpT4+Nj5hPT+kWT++nT8xKD9nSD+tqj9FYT/LmD8kNT+fUz+3oj85MT9jQT+nXD/Dmz8sIz9oTECxp0BBY0DQOkCiVkC8n0A0K0BlRUCrXkDImUAnUEC1pEA8NEBhPkCmWUDBnUEv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'; diff --git a/components/Moderators/utils/auditUtils.ts b/components/Moderators/utils/auditUtils.ts index 1141a2b05..1fddfd590 100644 --- a/components/Moderators/utils/auditUtils.ts +++ b/components/Moderators/utils/auditUtils.ts @@ -79,101 +79,3 @@ export const getAuditContentUrl = (entry: FlaggedContent): string | null => { return `/post/${document.id}/${document.slug ?? item?.slug ?? ''}`; } }; - -/** - * Get content preview text for truncation detection - */ -export const getAuditContentPreview = (entry: FlaggedContent): string => { - if (!entry.item) { - return 'Content not available'; - } - - const commentContent = entry.item.comment_content_json; - - if (!commentContent) { - return 'No content available'; - } - - // Handle string format (JSON string) - if (typeof commentContent === 'string') { - try { - const parsed = JSON.parse(commentContent); - return extractTextFromContentJson(parsed); - } catch { - return commentContent; // Fallback to raw string - } - } - - // Handle object format - if (typeof commentContent === 'object') { - return extractTextFromContentJson(commentContent); - } - - return 'Content format not supported'; -}; - -/** - * Extract plain text from various content JSON formats - */ -const extractTextFromContentJson = (contentJson: any): string => { - if (!contentJson) return ''; - - // Handle Quill Delta format: {"ops": [{"insert": "text"}]} - if (contentJson.ops && Array.isArray(contentJson.ops)) { - return contentJson.ops - .map((op: any) => { - if (typeof op.insert === 'string') { - return op.insert; - } - // Handle mentions: {"insert": {"mention": {"id": 123, "name": "User"}}} - if (op.insert && typeof op.insert === 'object' && op.insert.mention) { - return `@${op.insert.mention.name ?? 'User'}`; - } - return ''; - }) - .join('') - .trim(); - } - - // Handle TipTap format: {"type": "doc", "content": [...]} - if (contentJson.type === 'doc' && contentJson.content) { - return extractTextFromTipTapArray(contentJson.content); - } - - // Handle direct content array (sometimes TipTap comes without wrapper) - if (Array.isArray(contentJson.content)) { - return extractTextFromTipTapArray(contentJson.content); - } - - // Fallback: try to stringify and extract meaningful text - const str = JSON.stringify(contentJson); - const textMatch = str.match(/"text":"([^"]+)"/g); - if (textMatch) { - return textMatch.map((match) => match.replace(/"text":"([^"]+)"/, '$1')).join(' '); - } - - return 'Content format not recognized'; -}; - -/** - * Extract text from TipTap content array - */ -const extractTextFromTipTapArray = (content: any[]): string => { - if (!Array.isArray(content)) return ''; - - return content - .map((node: any) => { - if (node.type === 'text') { - return node.text ?? ''; - } - if (node.content && Array.isArray(node.content)) { - return extractTextFromTipTapArray(node.content); - } - if (node.type === 'mention' && node.attrs?.label) { - return `@${node.attrs.label}`; - } - return ''; - }) - .join('') - .trim(); -}; diff --git a/components/Notebook/PublishingForm/index.tsx b/components/Notebook/PublishingForm/index.tsx index 122d03af7..74a9bddbe 100644 --- a/components/Notebook/PublishingForm/index.tsx +++ b/components/Notebook/PublishingForm/index.tsx @@ -26,7 +26,6 @@ import { usePublishingController } from './PublishingFormProvider'; import type { PublishingFormData } from './schema'; export { PublishingFormProvider, usePublishingController } from './PublishingFormProvider'; -export { PublishButton } from './PublishButton'; export { usePublishingCompletion } from './completion'; const FEATURE_FLAG_RESEARCH_COIN = false; diff --git a/components/tooltips/HotScoreTooltip.tsx b/components/tooltips/HotScoreTooltip.tsx index bf1fed36c..62765d94f 100644 --- a/components/tooltips/HotScoreTooltip.tsx +++ b/components/tooltips/HotScoreTooltip.tsx @@ -102,6 +102,3 @@ export function PopularityScoreTooltip({ score, breakdown }: PopularityScoreTool
); } - -/** @deprecated Use PopularityScoreTooltip instead */ -export const HotScoreTooltip = PopularityScoreTooltip; diff --git a/components/ui/CollapsibleSection.tsx b/components/ui/CollapsibleSection.tsx index dd56c112a..9d769dc48 100644 --- a/components/ui/CollapsibleSection.tsx +++ b/components/ui/CollapsibleSection.tsx @@ -1,7 +1,7 @@ 'use client'; import React from 'react'; -import { ChevronUp, ChevronDown, ChevronRight } from 'lucide-react'; +import { ChevronUp, ChevronDown } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; interface CollapsibleSectionProps { @@ -83,64 +83,3 @@ export function CollapsibleSection({ ); } - -interface CollapsibleItemProps { - title: string; - children: React.ReactNode; - isOpen: boolean; - onToggle: () => void; - icon?: React.ReactNode; -} - -export function CollapsibleItem({ - title, - icon = , - isOpen = false, - children, -}: CollapsibleItemProps) { - return ( -
-
-
{icon}
-
-
{title}
- {isOpen &&
{children}
} -
-
-
- ); -} - -// Simple wrapper that manages its own state -interface SimpleCollapsibleSectionProps { - title: string; - children: React.ReactNode; - className?: string; - defaultExpanded?: boolean; -} - -export function SimpleCollapsibleSection({ - title, - children, - className = '', - defaultExpanded = true, -}: SimpleCollapsibleSectionProps) { - const [isExpanded, setIsExpanded] = React.useState(defaultExpanded); - - return ( - } - isExpanded={isExpanded} - onToggle={() => setIsExpanded(!isExpanded)} - className={className} - > - {children} - - ); -} diff --git a/components/ui/ContributorsButton.tsx b/components/ui/ContributorsButton.tsx index a50fb39ee..b010d9026 100644 --- a/components/ui/ContributorsButton.tsx +++ b/components/ui/ContributorsButton.tsx @@ -13,115 +13,6 @@ type FlexibleProfile = { [key: string]: any; // Allow any additional properties }; -interface LabelBadgeProps { - count: number; - label: string; - size?: number | 'xxs' | 'xs' | 'sm' | 'md' | 'lg'; - removeBadgeSpacing?: boolean; -} - -// Extract the label badge as a separate component -export function LabelBadge({ - count, - label, - size = 'xs', - removeBadgeSpacing = false, -}: LabelBadgeProps) { - // Use singular form if there's only one - const displayLabel = count === 1 ? label.replace(/s$/, '') : label; - - // Size-based styles - const getFontSize = () => { - if (typeof size === 'number') { - return `text-[${Math.max(size * 0.6, 12)}px]`; - } - - switch (size) { - case 'lg': - return 'text-base font-medium'; - case 'md': - return 'text-sm font-medium'; - case 'sm': - return 'text-xs font-medium'; - case 'xxs': - case 'xs': - default: - return 'text-xs'; - } - }; - - const getPadding = () => { - if (typeof size === 'number') { - const paddingX = Math.max(Math.floor(size * 0.25), 3); - const paddingY = Math.max(Math.floor(size * 0.1), 1); - return `px-${paddingX} py-${paddingY}`; - } - - switch (size) { - case 'lg': - return 'px-5 py-1.5'; - case 'md': - return 'px-4 py-1'; - case 'sm': - return 'px-3 py-0.5'; - case 'xxs': - case 'xs': - default: - return 'px-3 py-0.5'; - } - }; - - const getMargin = () => { - if (typeof size === 'number') { - return `-ml-[${Math.max(Math.floor(size * 0.3), 11)}px]`; - } - - switch (size) { - case 'lg': - return '-ml-[18px]'; - case 'md': - return '-ml-[16px]'; - case 'sm': - return '-ml-[14px]'; - case 'xxs': - case 'xs': - default: - return '-ml-[11px]'; - } - }; - - // Get ring size based on badge size - const getRingSize = () => { - if (typeof size === 'number') { - return size >= 32 ? 'ring-[3px]' : 'ring-2'; - } - - switch (size) { - case 'lg': - return 'ring-[3px]'; - case 'md': - return 'ring-[2.5px]'; - default: - return 'ring-2'; - } - }; - - return ( -
- - {count} {displayLabel} - -
- ); -} - interface ContributorsButtonProps { contributors: Array<{ profile: FlexibleProfile; diff --git a/components/ui/LoadErrorState.tsx b/components/ui/LoadErrorState.tsx index ac238699d..7f5b3338f 100644 --- a/components/ui/LoadErrorState.tsx +++ b/components/ui/LoadErrorState.tsx @@ -52,5 +52,3 @@ export const LoadErrorState = ({ ); }; - -export default LoadErrorState; diff --git a/components/ui/Popover.tsx b/components/ui/Popover.tsx index e92b8c6bf..d06064924 100644 --- a/components/ui/Popover.tsx +++ b/components/ui/Popover.tsx @@ -15,8 +15,6 @@ import { cn } from '@/utils/styles'; */ export const Popover = RadixPopover.Root; export const PopoverTrigger = RadixPopover.Trigger; -export const PopoverAnchor = RadixPopover.Anchor; -export const PopoverClose = RadixPopover.Close; export function PopoverContent({ className, diff --git a/components/ui/SearchEmpty.tsx b/components/ui/SearchEmpty.tsx index 24b437a93..3f32dac00 100644 --- a/components/ui/SearchEmpty.tsx +++ b/components/ui/SearchEmpty.tsx @@ -34,5 +34,3 @@ export const SearchEmpty = ({ ); }; - -export default SearchEmpty; diff --git a/components/ui/SwipeableDrawer.tsx b/components/ui/SwipeableDrawer.tsx index 74167f497..2a9acc69c 100644 --- a/components/ui/SwipeableDrawer.tsx +++ b/components/ui/SwipeableDrawer.tsx @@ -185,5 +185,3 @@ export const SwipeableDrawer: React.FC = ({ // Use createPortal to mount drawer to body return createPortal(drawerContent, document.body); }; - -export default SwipeableDrawer; diff --git a/components/ui/Table/index.ts b/components/ui/Table/index.ts index 83dc9176a..44455b47e 100644 --- a/components/ui/Table/index.ts +++ b/components/ui/Table/index.ts @@ -1,9 +1,3 @@ export { Table } from './Table'; -export { TableHead } from './TableHead'; -export { TableBody } from './TableBody'; -export { TableHeader, type SortDirection } from './TableHeader'; -export { TableRow } from './TableRow'; -export { TableCell } from './TableCell'; export { TableContainer, type SortableColumn } from './TableContainer'; -export { MobileTableCard, type MobileTableCardProps } from './MobileTableCard'; -export { TableSkeleton, type TableSkeletonProps } from './TableSkeleton'; +export { MobileTableCard } from './MobileTableCard'; diff --git a/components/ui/TopicAndJournalBadge.tsx b/components/ui/TopicAndJournalBadge.tsx deleted file mode 100644 index 1ab226fb4..000000000 --- a/components/ui/TopicAndJournalBadge.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Link from 'next/link'; -import { Badge } from './Badge'; - -export type BadgeType = 'topic' | 'journal'; -export type BadgeSize = 'sm' | 'md' | 'lg'; -export type BadgeVariant = 'primary' | 'secondary'; - -export interface TopicAndJournalBadgeProps { - name: string; - slug: string; - className?: string; - disableLink?: boolean; - size?: BadgeSize; -} - -/** - * A reusable badge component for topics and journals - * Displays an avatar if imageUrl is provided - * Links to the appropriate page based on type and slug - * Can optionally disable the link functionality with disableLink prop - * Supports different sizes: sm, md (default), lg - * Supports different variants: primary (default), secondary - */ -export const TopicAndJournalBadge = ({ - name, - slug, - className = '', - disableLink = false, - size = 'md', -}: TopicAndJournalBadgeProps) => { - const href = `/topic/${slug}`; - - const sizePadding = { - sm: 'px-1.5 py-0.5', - md: 'px-2 py-1', - lg: 'px-2 py-1', - }; - - const badge = ( - - {name} - - ); - - return disableLink ? ( - badge - ) : ( - - {badge} - - ); -}; diff --git a/components/work/WorkHeader/index.ts b/components/work/WorkHeader/index.ts index 960d346ff..4e1addc70 100644 --- a/components/work/WorkHeader/index.ts +++ b/components/work/WorkHeader/index.ts @@ -1,4 +1,4 @@ export { WorkHeader } from './WorkHeader'; export { WorkHeaderGrant } from './WorkHeaderGrant'; export { WorkHeaderProposal } from './WorkHeaderProposal'; -export { WorkTabProvider, useWorkTab } from './WorkTabContext'; +export { WorkTabProvider } from './WorkTabContext'; diff --git a/contexts/AuthModalContext.tsx b/contexts/AuthModalContext.tsx index 6cd0720dc..b826fd3e9 100644 --- a/contexts/AuthModalContext.tsx +++ b/contexts/AuthModalContext.tsx @@ -3,7 +3,6 @@ import { createContext, useContext, useState, useCallback } from 'react'; import AuthModal from '@/components/modals/Auth/AuthModal'; import { useSession } from 'next-auth/react'; -import { ReactNode } from 'react'; import AnalyticsService, { LogEvent } from '@/services/analytics.service'; interface AuthModalContextType { @@ -73,18 +72,3 @@ export function useAuthenticatedAction() { return { executeAuthenticatedAction }; } - -interface AuthenticatedContentProps { - children: ReactNode; - fallback?: ReactNode; -} - -export const AuthenticatedContent = ({ children, fallback = null }: AuthenticatedContentProps) => { - const { status } = useSession(); - - if (status !== 'authenticated') { - return fallback; - } - - return <>{children}; -}; diff --git a/hooks/useBounties.ts b/hooks/useBounties.ts index f91c46cab..96f66d96e 100644 --- a/hooks/useBounties.ts +++ b/hooks/useBounties.ts @@ -4,7 +4,13 @@ import { FeedEntry } from '@/types/feed'; import { BountyService } from '@/services/bounty.service'; import { useNavigation, getFeedKey } from '@/contexts/NavigationContext'; import { useClickContext } from '@/contexts/ClickContext'; -import { Hub } from '@/components/Earn/BountyHubSelector'; + +interface Hub { + id: string | number; + name: string; + description?: string; + color?: string; +} export const useBounties = () => { const pathname = usePathname(); diff --git a/hooks/useNote.ts b/hooks/useNote.ts index 9da2a5b0d..4a1f5ded3 100644 --- a/hooks/useNote.ts +++ b/hooks/useNote.ts @@ -1,12 +1,6 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { NoteService, NoteError, type NoteInvitePreview } from '@/services/note.service'; -import { - isChangelogNote, - type NoteWithContent, - type Note, - type NoteAccess, - type NoteContent, -} from '@/types/note'; +import { isChangelogNote, type Note, type NoteAccess, type NoteContent } from '@/types/note'; import { ID } from '@/types/root'; import { Editor } from '@tiptap/react'; import { getHTMLFromFragment, getText, getTextSerializersFromSchema } from '@tiptap/core'; @@ -84,73 +78,6 @@ export function useAcceptNoteInvite(): UseAcceptNoteInviteReturn { return [{ isLoading, error }, acceptInvite]; } -interface UseNoteState { - note: NoteWithContent | null; - isLoading: boolean; - error: Error | null; -} - -type FetchNoteFn = () => Promise; -type UseNoteReturn = [UseNoteState, FetchNoteFn]; - -/** - * Custom hook to fetch and manage note data. - * Accepts an optional initialNote to prevent unnecessary fetching when data is already available. - * - * TODO: Future Collaboration Implementation - * This hook will be updated to use TipTap's collaboration features: - * 1. Initialize Y.js document - * 2. Configure TipTap collaboration extension - * 3. Set up WebSocket connection via TiptapCollabProvider - * 4. Handle real-time updates and presence - * - * Example future implementation: - * ```typescript - * const doc = new Y.Doc(); - * const editor = useEditor({ - * extensions: [ - * StarterKit.configure({ history: false }), // Disable for collaboration - * Collaboration.configure({ document: doc }), - * // Add cursor presence, etc. - * ], - * }); - * ``` - * - * @param noteId - The ID of the note to fetch - * @returns UseNoteReturn object containing note data and loading state - */ -export function useNote( - noteId: string, - options: UseNoteOptions = { sendImmediately: true } -): UseNoteReturn { - const [note, setNote] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const fetch = useCallback(async () => { - try { - setIsLoading(true); - setError(null); - - const noteData = await NoteService.getNote(noteId); - setNote(noteData); - } catch (err) { - setError(err instanceof Error ? err : new Error('Failed to fetch note')); - setNote(null); - } finally { - setIsLoading(false); - } - }, [noteId]); - - useEffect(() => { - if (options.sendImmediately !== false) { - fetch(); - } - }, [fetch, options.sendImmediately]); - - return [{ note, isLoading, error }, fetch]; -} - interface CreateNoteInput { title: string; grouping: NoteAccess; @@ -474,80 +401,6 @@ export const useUpdateNote = (noteId: ID, options: UpdateNoteOptions = {}): UseU return [{ isLoading, error }, updateNote, saveNoteNow]; }; -interface UseMakeNotePrivateState { - isLoading: boolean; - error: Error | null; -} - -type MakeNotePrivateFn = (noteId: ID) => Promise; -type UseMakeNotePrivateReturn = [UseMakeNotePrivateState, MakeNotePrivateFn]; - -export const useMakeNotePrivate = (): UseMakeNotePrivateReturn => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const makeNotePrivate = async (noteId: ID) => { - setIsLoading(true); - setError(null); - - try { - const response = await NoteService.makePrivate(noteId); - return response; - } catch (err) { - const errorMsg = err instanceof NoteError ? err.message : 'Failed to make note private'; - const error = new Error(errorMsg); - setError(error); - throw error; - } finally { - setIsLoading(false); - } - }; - - return [{ isLoading, error }, makeNotePrivate]; -}; - -interface UpdateNotePermissionsInput { - noteId: ID; - organizationId: ID; - accessType?: 'ADMIN'; -} - -interface UseUpdateNotePermissionsState { - isLoading: boolean; - error: Error | null; -} - -type UpdateNotePermissionsFn = (params: UpdateNotePermissionsInput) => Promise; -type UseUpdateNotePermissionsReturn = [UseUpdateNotePermissionsState, UpdateNotePermissionsFn]; - -export const useUpdateNotePermissions = (): UseUpdateNotePermissionsReturn => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const updatePermissions = async (params: UpdateNotePermissionsInput) => { - setIsLoading(true); - setError(null); - - try { - const response = await NoteService.updateNotePermissions( - params.noteId, - params.organizationId, - params.accessType - ); - return response; - } catch (err) { - const errorMsg = err instanceof NoteError ? err.message : 'Failed to update note permissions'; - const error = new Error(errorMsg); - setError(error); - throw error; - } finally { - setIsLoading(false); - } - }; - - return [{ isLoading, error }, updatePermissions]; -}; - interface UseDuplicateNoteState { isLoading: boolean; error: Error | null; diff --git a/hooks/useOrganization.ts b/hooks/useOrganization.ts index 356018b47..4da0bfa1b 100644 --- a/hooks/useOrganization.ts +++ b/hooks/useOrganization.ts @@ -4,45 +4,6 @@ import { useState, useCallback } from 'react'; import { OrganizationService, OrganizationError } from '@/services/organization.service'; import { Organization } from '@/types/organization'; -interface UseUpdateOrgDetailsState { - data: Organization | null; - isLoading: boolean; - error: string | null; -} - -type UpdateOrgDetailsFn = (orgId: string, name: string) => Promise; -type UseUpdateOrgDetailsReturn = [UseUpdateOrgDetailsState, UpdateOrgDetailsFn]; - -/** - * Hook for updating organization details - * @returns A tuple containing the state and update function - */ -export const useUpdateOrgDetails = (): UseUpdateOrgDetailsReturn => { - const [data, setData] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const updateOrgDetails = async (orgId: string, name: string): Promise => { - setIsLoading(true); - setError(null); - - try { - const response = await OrganizationService.updateOrgDetails(orgId, name); - setData(response); - return response; - } catch (err) { - const errorMsg = - err instanceof OrganizationError ? err.message : 'Failed to update organization details'; - setError(errorMsg); - throw new Error(errorMsg); - } finally { - setIsLoading(false); - } - }; - - return [{ data, isLoading, error }, updateOrgDetails]; -}; - interface UseInviteUserToOrgState { isLoading: boolean; error: string | null; @@ -289,50 +250,3 @@ export const useFetchOrgByInviteToken = () => { return [{ data, isLoading, error }, fetchOrgByInviteToken] as const; }; - -interface UseUpdateOrgCoverImageState { - data: Organization | null; - isLoading: boolean; - error: string | null; -} - -type UpdateOrgCoverImageFn = ( - orgId: string | number, - coverImage: File | Blob -) => Promise; -type UseUpdateOrgCoverImageReturn = [UseUpdateOrgCoverImageState, UpdateOrgCoverImageFn]; - -/** - * Hook for updating an organization's cover image - * @returns A tuple containing the state and update function - */ -export const useUpdateOrgCoverImage = (): UseUpdateOrgCoverImageReturn => { - const [data, setData] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const updateOrgCoverImage = async ( - orgId: string | number, - coverImage: File | Blob - ): Promise => { - setIsLoading(true); - setError(null); - - try { - const response = await OrganizationService.updateOrgCoverImage(orgId, coverImage); - setData(response); - return response; - } catch (err) { - const errorMsg = - err instanceof OrganizationError - ? err.message - : 'Failed to update organization cover image'; - setError(errorMsg); - throw new Error(errorMsg); - } finally { - setIsLoading(false); - } - }; - - return [{ data, isLoading, error }, updateOrgCoverImage]; -}; diff --git a/hooks/usePinchZoom.ts b/hooks/usePinchZoom.ts index a169188dc..4f7a3eab3 100644 --- a/hooks/usePinchZoom.ts +++ b/hooks/usePinchZoom.ts @@ -380,10 +380,3 @@ export function usePinchZoom({ transformOrigin, }; } - -/** - * Utility to detect if a touch event is a pinch gesture - */ -export function isPinchGesture(e: TouchEvent): boolean { - return e.touches.length === 2; -} diff --git a/lib/utils.ts b/lib/utils.ts index a850cd0de..9e95658f6 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -7,8 +7,3 @@ import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } - -export function toNumberOrNull(value: any): number | null { - const num = Number(value); - return Number.isFinite(num) ? num : null; -} diff --git a/package-lock.json b/package-lock.json index f895678db..2e75b375d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "@tippyjs/react": "^4.2.6", "@tiptap-pro/extension-import": "3.9.2", "@tiptap/core": "3.29.2", - "@tiptap/extension-bullet-list": "3.29.2", "@tiptap/extension-character-count": "3.29.2", "@tiptap/extension-code-block": "3.29.2", "@tiptap/extension-code-block-lowlight": "3.29.2", @@ -55,8 +54,6 @@ "@tiptap/extension-link": "3.29.2", "@tiptap/extension-mathematics": "3.29.2", "@tiptap/extension-mention": "3.29.2", - "@tiptap/extension-ordered-list": "3.29.2", - "@tiptap/extension-paragraph": "3.29.2", "@tiptap/extension-placeholder": "3.29.2", "@tiptap/extension-subscript": "3.29.2", "@tiptap/extension-superscript": "3.29.2", diff --git a/package.json b/package.json index 12e0c53d9..30a540fa9 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "@tippyjs/react": "^4.2.6", "@tiptap-pro/extension-import": "3.9.2", "@tiptap/core": "3.29.2", - "@tiptap/extension-bullet-list": "3.29.2", "@tiptap/extension-character-count": "3.29.2", "@tiptap/extension-code-block": "3.29.2", "@tiptap/extension-code-block-lowlight": "3.29.2", @@ -64,8 +63,6 @@ "@tiptap/extension-link": "3.29.2", "@tiptap/extension-mathematics": "3.29.2", "@tiptap/extension-mention": "3.29.2", - "@tiptap/extension-ordered-list": "3.29.2", - "@tiptap/extension-paragraph": "3.29.2", "@tiptap/extension-placeholder": "3.29.2", "@tiptap/extension-subscript": "3.29.2", "@tiptap/extension-superscript": "3.29.2", diff --git a/services/author.service.ts b/services/author.service.ts index 8b47f5dd8..3497412ba 100644 --- a/services/author.service.ts +++ b/services/author.service.ts @@ -43,11 +43,6 @@ export interface AuthorUpdatePayload { google_scholar?: string | null; } -// Interface for the update payload parameters -export interface AuthorUpdateParams extends AuthorUpdatePayload { - profileImageDataUrl?: string | null; // Add image data URL -} - export class AuthorService { private static readonly AUTHORS_PATH = '/api/author'; diff --git a/services/content-moderation.service.ts b/services/content-moderation.service.ts index 56507c680..364d0964a 100644 --- a/services/content-moderation.service.ts +++ b/services/content-moderation.service.ts @@ -3,8 +3,6 @@ import { ID } from '@/types/root'; import { FeedService } from './feed.service'; import { GrantModerationService, type PendingWorksResponse } from './grant-moderation.service'; -export type { PendingWorksResponse }; - export type PendingModule = 'funding_opportunities' | 'proposals'; /** Number of pending items per module, as shown on the tab badges. */ diff --git a/services/hub.service.ts b/services/hub.service.ts index 205f95292..b5528c7a0 100644 --- a/services/hub.service.ts +++ b/services/hub.service.ts @@ -1,6 +1,5 @@ import { Topic, transformTopicSuggestions, transformTopic } from '@/types/topic'; import { ApiClient } from './client'; -import { createTransformer, BaseTransformed } from '@/types/transformer'; interface HubResponse { id: number; @@ -38,17 +37,6 @@ export interface Hub { description?: string; } -export type TransformedHub = Hub & BaseTransformed; - -// Transform the API response to our internal Hub model -export const transformHub = createTransformer((hub) => ({ - id: hub.id, - name: hub.name, - slug: hub.slug, - imageUrl: hub.hub_image || undefined, - description: hub.description, -})); - export class HubService { private static readonly BASE_PATH = '/api/hub'; private static readonly SUGGEST_PATH = '/api/search/hubs/suggest'; diff --git a/services/reaction.service.ts b/services/reaction.service.ts index b22474186..a2c6aac0c 100644 --- a/services/reaction.service.ts +++ b/services/reaction.service.ts @@ -56,11 +56,6 @@ export interface VoteOnCommentOptions { documentType: DocumentType; } -export interface MarkNotInterestedOptions { - documentType: DocumentType; - documentId: ID; -} - export class ReactionService { private static readonly BASE_PATH = '/api'; diff --git a/services/types/api.ts b/services/types/api.ts index a9f5710bc..2e73cd709 100644 --- a/services/types/api.ts +++ b/services/types/api.ts @@ -1,9 +1,4 @@ // API Infrastructure -export interface ApiResponse { - data: T; - message?: string; -} - export class ApiError extends Error { constructor( public message: string, diff --git a/services/types/notification.dto.ts b/services/types/notification.dto.ts index a9826d1ea..a2ab84fe3 100644 --- a/services/types/notification.dto.ts +++ b/services/types/notification.dto.ts @@ -12,7 +12,6 @@ export interface NotificationCountResponse { count: number; } -export type TransformedNotificationListResponse = NotificationListResponse & BaseTransformed; export type TransformedNotificationCountResponse = NotificationCountResponse & BaseTransformed; export const transformNotificationListResponse = createTransformer( diff --git a/types/authorProfile.ts b/types/authorProfile.ts index c63d7d067..12a3ea6e8 100644 --- a/types/authorProfile.ts +++ b/types/authorProfile.ts @@ -1,4 +1,4 @@ -import { createTransformer, BaseTransformed } from './transformer'; +import { createTransformer } from './transformer'; import { User, transformUser } from './user'; import { Topic, transformTopic } from './topic'; @@ -46,8 +46,6 @@ export interface AuthorProfile { isHubEditor?: boolean; } -export type TransformedAuthorProfile = AuthorProfile & BaseTransformed; - export function extractHeadline(value: unknown): string { if (!value) return ''; if (typeof value === 'string') return value; diff --git a/types/comment.ts b/types/comment.ts index 414a79538..5889c83cc 100644 --- a/types/comment.ts +++ b/types/comment.ts @@ -81,13 +81,6 @@ export const transformThread: BaseTransformer = (raw) => ({ raw, }); -export const transformContent = (raw: any): string => { - if (raw.html) { - return raw.html; - } - return raw.comment_content_json || ''; -}; - export const transformComment = (raw: any): Comment => { // Transform user_vote from API // It can be either an object with vote_type or a direct numeric value diff --git a/types/feed.ts b/types/feed.ts index 1f0769e4c..429801c8b 100644 --- a/types/feed.ts +++ b/types/feed.ts @@ -1,15 +1,8 @@ import { AuthorProfile, transformAuthorProfile } from './authorProfile'; import { ContentMetrics } from './metrics'; import { Topic, transformTopic } from './topic'; -import { createTransformer, BaseTransformed } from './transformer'; -import { - Work, - transformPaper, - transformPost, - FundingRequest, - ContentType, - type WorkGrantSummary, -} from './work'; +import { createTransformer } from './transformer'; +import { Work, transformPaper, transformPost, ContentType, type WorkGrantSummary } from './work'; import { mapApiDocumentTypeToClientType, type ApiDocumentType } from '@/utils/contentTypeMapping'; import { Bounty, BountyWithComment, transformBounty } from './bounty'; import { Comment, CommentType, ContentFormat, transformComment } from './comment'; @@ -24,9 +17,6 @@ import { FundingPool, GrantStatus, transformFundingPool } from './grant'; export type FeedActionType = 'contribute' | 'open' | 'publish' | 'post'; -// Re-export FundingRequest for backward compatibility -export type { FundingRequest }; - // Simplified structure for parent comment preview - now recursive export interface ParentCommentPreview { id: number; @@ -626,9 +616,6 @@ export function getUnifiedDocumentId(content_object: any): string | undefined { return undefined; } -export type TransformedContent = Content & BaseTransformed; -export type TransformedFeedEntry = FeedEntry & BaseTransformed; - function transformActivityRelatedWorkGrant(rawGrant: unknown): WorkGrantSummary | undefined { if (!rawGrant || typeof rawGrant !== 'object') return undefined; @@ -1582,92 +1569,6 @@ export const transformBountyCommentToFeedItem = ( }; }; -/** - * Creates a FeedEntry from a Work object, typically used for bounty entries - * where we need to display the related work (paper or post) as a feed item. - * @param work The Work object to transform into a FeedEntry - * @param originalEntry The original FeedEntry to extract metadata from (metrics, userVote, tips) - * @returns A FeedEntry object with the work as content, or null if the work cannot be transformed - */ -export const createFeedEntryFromWork = (work: Work, originalEntry: FeedEntry): FeedEntry | null => { - if (!work) return null; - - const contentType = work.contentType === 'paper' ? 'PAPER' : 'POST'; - if (!contentType) return null; - - const bountyEntry = originalEntry.content as FeedBountyContent; - const originalBounty = bountyEntry.bounty; - const bountyWithComment: BountyWithComment = { - ...originalBounty, - comment: bountyEntry.comment, - }; - - // Transform Work to FeedPostContent or FeedPaperContent - if (contentType === 'POST') { - const postContent: FeedPostContent = { - id: work.id, - bounties: [bountyWithComment], - contentType: work.postType === 'PREREGISTRATION' ? 'PREREGISTRATION' : 'POST', - createdDate: work.createdDate, - createdBy: work.authors?.[0]?.authorProfile || bountyEntry.createdBy, - textPreview: work.previewContent || work.abstract || '', - slug: work.slug, - title: work.title, - previewImage: work.image, - authors: work.authors?.map((a: { authorProfile: any }) => a.authorProfile) || [], - topics: work.topics || [], - postType: work.postType, - }; - - return { - id: work.id.toString(), - recommendationId: null, - timestamp: work.createdDate, - action: 'publish', - contentType: contentType, - content: postContent, - relatedWork: undefined, - metrics: originalEntry.metrics, - userVote: originalEntry.userVote, - tips: originalEntry.tips, - }; - } else if (contentType === 'PAPER') { - const paperContent: FeedPaperContent = { - id: work.id, - bounties: [bountyWithComment], - contentType: 'PAPER', - createdDate: work.createdDate, - createdBy: work.authors?.[0]?.authorProfile || bountyEntry.createdBy, - textPreview: work.abstract || '', - slug: work.slug, - title: work.title, - authors: work.authors?.map((a: { authorProfile: any }) => a.authorProfile) || [], - topics: work.topics || [], - journal: work.journal || { - id: 0, - name: '', - slug: '', - description: '', - }, - }; - - return { - id: work.id.toString(), - recommendationId: null, - timestamp: work.createdDate, - action: 'publish', - contentType: 'PAPER', - content: paperContent, - relatedWork: undefined, - metrics: originalEntry.metrics, - userVote: originalEntry.userVote, - tips: originalEntry.tips, - }; - } - - return null; -}; - export type { AuthorProfile }; export type { Fundraise }; diff --git a/types/leaderboard.ts b/types/leaderboard.ts index e0dc28dc0..ede60679b 100644 --- a/types/leaderboard.ts +++ b/types/leaderboard.ts @@ -75,9 +75,6 @@ export interface LeaderboardMeResponse { funder: RawTopFunder | null; } -export type LeaderboardReviewersResponse = RawTopReviewer[]; -export type LeaderboardFundersResponse = RawTopFunder[]; - // Transformer for TopReviewer export const transformTopReviewer = createTransformer( (raw: RawTopReviewer) => { diff --git a/types/note.ts b/types/note.ts index 03b6a10d2..053f97840 100644 --- a/types/note.ts +++ b/types/note.ts @@ -1,6 +1,6 @@ import { CHANGELOG_NOTEBOOK_ROLLOUT_AT, CHANGELOG_POST_IDS } from '@/constants/changelog'; import type { Organization } from './organization'; -import { createTransformer, BaseTransformed } from './transformer'; +import { createTransformer } from './transformer'; import { transformOrganization } from './organization'; import { Currency, ID } from './root'; import { ContentType, ModerationStatus } from './work'; @@ -221,22 +221,6 @@ export interface NoteVersionEvent { export const NOTE_VERSION_CREATED = 'note_version_created'; -export interface NoteApiItem { - id: number; - access: NoteAccess; - organization: { - id: number; - cover_image: string | null; - name: string; - slug: string; - }; - created_date: string; - updated_date: string; - title: string; -} - -export type TransformedNote = Note & BaseTransformed; - const buildFullName = (raw: any): string => `${raw.first_name || ''} ${raw.last_name || ''}`.trim() || 'Unknown'; diff --git a/types/notification.ts b/types/notification.ts index cda66aa1f..28ec864bb 100644 --- a/types/notification.ts +++ b/types/notification.ts @@ -49,9 +49,7 @@ export interface Notification { createdDate: Date; } -export type TransformedNotification = Notification & BaseTransformed; export type TransformedNotificationExtra = NotificationExtra & BaseTransformed; -export type TransformedNotificationBodyElement = NotificationBodyElement & BaseTransformed; export type NotificationForWork = NonNullable; diff --git a/types/search.ts b/types/search.ts index 1a7e81836..c87ea8f29 100644 --- a/types/search.ts +++ b/types/search.ts @@ -1,4 +1,4 @@ -import { createTransformer, BaseTransformed } from './transformer'; +import { createTransformer } from './transformer'; import { buildWorkUrl, buildAuthorUrl, buildTopicUrl } from '@/utils/url'; import { mapApiDocumentTypeToClientType } from '@/utils/contentTypeMapping'; import { AuthorProfile, extractHeadline } from './authorProfile'; @@ -6,7 +6,6 @@ import { ID } from './root'; import { transformTopic } from './topic'; import { ContentType } from './work'; -export type SuggestionSource = 'api' | 'recent' | 'researchhub' | 'openalex'; export type EntityType = 'user' | 'paper' | 'author' | 'post' | 'hub'; export interface BaseSuggestion { @@ -62,17 +61,6 @@ export interface TopicSuggestion extends BaseSuggestion { export type SearchSuggestion = WorkSuggestion | UserSuggestion | PostSuggestion | TopicSuggestion; -export interface RecentPageView { - id: number; - title: string; - doi?: string; - authors: string[]; - lastVisited: string; - slug: string; -} - -export type TransformedSearchSuggestion = SearchSuggestion & BaseTransformed; - export const transformSearchSuggestion = createTransformer((raw: any) => { try { if (raw.lastVisited) { diff --git a/types/work.ts b/types/work.ts index cfe025602..dbec4533c 100644 --- a/types/work.ts +++ b/types/work.ts @@ -146,22 +146,7 @@ export interface WorkGrantSummary { endDate?: string; } -export interface FundingRequest extends Work { - type: 'funding_request'; - contentType: 'funding_request'; - status: 'OPEN' | 'CLOSED' | 'FUNDED'; - amount: number; - goalAmount: number; - deadline: string; - preregistered?: boolean; - image?: string; -} - -// Transformed types -export type TransformedAuthorship = Authorship & BaseTransformed; export type TransformedJournal = Journal & BaseTransformed; -export type TransformedTopic = Topic & BaseTransformed; -export type TransformedDocumentVersion = DocumentVersion & BaseTransformed; export type TransformedWork = Work & BaseTransformed; // Transformers diff --git a/utils/number.ts b/utils/number.ts index 03650c2b4..788bda69e 100644 --- a/utils/number.ts +++ b/utils/number.ts @@ -200,57 +200,6 @@ export function extractExchangeRate( return 0; } -interface FormatCombinedBalanceOptions { - balance: BalanceData | null; - lockedBalance: BalanceData | null; - showUSD: boolean; - includeRSCSuffix?: boolean; -} - -/** - * Formats the combined balance (available + locked) in the preferred currency - * @param options Formatting options - * @returns Formatted string in the preferred currency - */ -export function formatCombinedBalance({ - balance, - lockedBalance, - showUSD, - includeRSCSuffix = true, -}: FormatCombinedBalanceOptions): string { - const totalRaw = (balance?.raw || 0) + (lockedBalance?.raw || 0); - - if (showUSD) { - const exchangeRate = extractExchangeRate(balance, lockedBalance); - return exchangeRate > 0 ? formatUsdValue(totalRaw.toString(), exchangeRate) : '$0.00'; - } else { - const formatted = formatRSC({ amount: totalRaw }); - return includeRSCSuffix ? `${formatted} RSC` : formatted; - } -} - -/** - * Gets the secondary currency display for combined balance - * @param options Formatting options - * @returns Formatted string in the secondary currency - */ -export function formatCombinedBalanceSecondary({ - balance, - lockedBalance, - showUSD, -}: FormatCombinedBalanceOptions): string { - const totalRaw = (balance?.raw || 0) + (lockedBalance?.raw || 0); - - if (showUSD) { - // Secondary is RSC when primary is USD - return `${formatRSC({ amount: totalRaw })} RSC`; - } else { - // Secondary is USD when primary is RSC - const exchangeRate = extractExchangeRate(balance, lockedBalance); - return exchangeRate > 0 ? formatUsdValue(totalRaw.toString(), exchangeRate) : '$0.00 USD'; - } -} - /** * Parse a decimal amount from user input. Accepts optional thousand separators * (`,` or spaces) and a `.` decimal; rejects trailing/leading junk that diff --git a/utils/preprintUtil.ts b/utils/preprintUtil.ts index ff3b060e9..194ea179e 100644 --- a/utils/preprintUtil.ts +++ b/utils/preprintUtil.ts @@ -29,9 +29,6 @@ export const isPreprintServer = (slug: string): boolean => { return ['arxiv', 'biorxiv', 'chemrxiv', 'medrxiv'].includes(slug.toLowerCase()); }; -// Alias for getSourceLogo (for consistency with other naming conventions) -export const get_document_metadata = getSourceLogo; - // Get display name for preprint servers export const getPreprintDisplayName = (slug: string): string => { const sourceLower = slug.toLowerCase(); diff --git a/utils/url.ts b/utils/url.ts index 8d7b9aa70..3b2652d32 100644 --- a/utils/url.ts +++ b/utils/url.ts @@ -1,15 +1,6 @@ import { withShareToken } from '@/lib/shareToken/url'; import type { ContentType } from '@/types/work'; -export const ALL_CONTENT_TYPES: readonly ContentType[] = [ - 'paper', - 'post', - 'preregistration', - 'question', - 'discussion', - 'funding_request', -]; - // URL path segment (first path segment) → ContentType. Aligns with app routes: /paper/, /post/, /question/, /proposal/, /fund/, /grant/ const ROUTE_SEGMENT_TO_CONTENT_TYPE: Record = { paper: 'paper', diff --git a/utils/validation.ts b/utils/validation.ts index 12473b228..ae7a416f3 100644 --- a/utils/validation.ts +++ b/utils/validation.ts @@ -2,11 +2,3 @@ export const isValidEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; - -export const isValidPassword = (password: string): boolean => { - return password.length >= 8; -}; - -export const isValidName = (name: string): boolean => { - return name.trim().length > 0; -};