diff --git a/.env.example b/.env.example index 84628a6e3..e0cadf565 100755 --- a/.env.example +++ b/.env.example @@ -40,8 +40,6 @@ NEXT_PUBLIC_API_URL=http://localhost:8000 NEXT_PUBLIC_PERSONA_ENVIRONMENT_ID=example NEXT_PUBLIC_PERSONA_TEMPLATE_ID=example -NEXT_PUBLIC_STORAGE_DOMAIN=example -NEXT_PUBLIC_WEB3_WALLET_ADDRESS=example NEXT_PUBLIC_AMPLITUDE_API_KEY='example' GA_MEASUREMENT_ID='example' diff --git a/app/expert-finder/components/SendConfirmationModal.tsx b/app/expert-finder/components/SendConfirmationModal.tsx deleted file mode 100644 index 4936b74a1..000000000 --- a/app/expert-finder/components/SendConfirmationModal.tsx +++ /dev/null @@ -1,92 +0,0 @@ -'use client'; - -import type { ReactNode } from 'react'; -import { Loader2 } from 'lucide-react'; -import { Button } from '@/components/ui/Button'; -import { Input } from '@/components/ui/form/Input'; -import { Modal } from '@/components/ui/form/Modal'; -import { - parseAndValidateReplyToInput, - REPLY_TO_MAX, -} from '@/app/expert-finder/lib/parseReplyToAddresses'; - -/** Modal to confirm a send/preview action; collects Reply-To for the API. */ -export interface SendConfirmationModalProps { - isOpen: boolean; - onClose: () => void; - isSubmitting: boolean; - title: string; - /** Shown above the Reply To field (optional) */ - description?: ReactNode; - replyTo: string; - onReplyToChange: (value: string) => void; - onConfirm: () => void; - confirmLabel?: string; - submittingLabel?: string; - confirmIcon?: ReactNode; - cancelLabel?: string; -} - -export function SendConfirmationModal({ - isOpen, - onClose, - isSubmitting, - title, - description, - replyTo, - onReplyToChange, - onConfirm, - confirmLabel = 'Send', - submittingLabel = 'Sending…', - confirmIcon, - cancelLabel = 'Cancel', -}: SendConfirmationModalProps) { - const replyValidation = parseAndValidateReplyToInput(replyTo); - const replyError = replyTo.trim() && !replyValidation.valid ? replyValidation.error : undefined; - const canSubmit = !isSubmitting && replyValidation.valid; - - return ( - !isSubmitting && onClose()} title={title}> - {description != null ?
{description}
: null} -
- onReplyToChange(e.target.value)} - placeholder="you@example.com" - helperText={`When experts hit Reply, their response goes to these addresses. Separate multiple with commas (max ${REPLY_TO_MAX}).`} - error={replyError} - /> -
-
- - -
-
- ); -} diff --git a/app/expert-finder/lib/outreachReplyToStorage.ts b/app/expert-finder/lib/outreachReplyToStorage.ts deleted file mode 100644 index 72107caa9..000000000 --- a/app/expert-finder/lib/outreachReplyToStorage.ts +++ /dev/null @@ -1,20 +0,0 @@ -const STORAGE_KEY = 'expert_finder_outreach_reply_to'; - -export function readOutreachReplyToFromStorage(): string { - if (typeof window === 'undefined') return ''; - try { - const v = window.localStorage.getItem(STORAGE_KEY); - return typeof v === 'string' ? v : ''; - } catch { - return ''; - } -} - -export function writeOutreachReplyToToStorage(value: string): void { - if (typeof window === 'undefined') return; - try { - window.localStorage.setItem(STORAGE_KEY, value); - } catch { - // ignore quota / private mode - } -} diff --git a/app/expert-finder/lib/parseReplyToAddresses.ts b/app/expert-finder/lib/parseReplyToAddresses.ts deleted file mode 100644 index 3997d62b2..000000000 --- a/app/expert-finder/lib/parseReplyToAddresses.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { isValidEmail } from '@/utils/validation'; - -export const REPLY_TO_MIN = 1; -export const REPLY_TO_MAX = 10; - -export function parseReplyToAddresses(input: string): string[] { - return input - .split(',') - .map((part) => part.trim()) - .filter((part) => part.length > 0); -} - -export function validateReplyToAddresses( - emails: string[] -): { valid: true } | { valid: false; error: string } { - if (emails.length < REPLY_TO_MIN) { - return { valid: false, error: 'Please enter at least one Reply To email address.' }; - } - if (emails.length > REPLY_TO_MAX) { - return { valid: false, error: `You can add at most ${REPLY_TO_MAX} Reply To addresses.` }; - } - const invalid = emails.find((email) => !isValidEmail(email)); - if (invalid != null) { - return { valid: false, error: `"${invalid}" is not a valid email address.` }; - } - return { valid: true }; -} - -export function parseAndValidateReplyToInput( - input: string -): { valid: true; emails: string[] } | { valid: false; error: string } { - const emails = parseReplyToAddresses(input); - const result = validateReplyToAddresses(emails); - if (!result.valid) return result; - return { valid: true, emails }; -} diff --git a/app/notebook/robots.ts b/app/notebook/robots.ts deleted file mode 100644 index ae003f310..000000000 --- a/app/notebook/robots.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { MetadataRoute } from 'next'; - -export default function robots(): MetadataRoute.Robots { - return { - rules: { - allow: '*', - }, - }; -} diff --git a/components/Comment/lib/SuggestionPortal.tsx b/components/Comment/lib/SuggestionPortal.tsx deleted file mode 100644 index 128770e0f..000000000 --- a/components/Comment/lib/SuggestionPortal.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useRef, useEffect, useLayoutEffect } from 'react'; -import { createPortal } from 'react-dom'; -import { cn } from '@/utils/styles'; -import { UserMention } from '@/types/comment'; -import { Editor } from '@tiptap/core'; -import tippy from 'tippy.js'; -import 'tippy.js/dist/tippy.css'; -import './mention.css'; - -interface SuggestionPortalProps { - items: UserMention[]; - command: (item: UserMention) => void; - clientRect: () => DOMRect; - editor: Editor; - className?: string; -} - -export const SuggestionPortal = ({ - items, - command, - clientRect, - className, -}: SuggestionPortalProps) => { - const containerRef = useRef(null); - const tippyInstance = useRef(null); - const portalRef = useRef(null); - const [mounted, setMounted] = React.useState(false); - - // Use layout effect to ensure DOM measurements are accurate - useLayoutEffect(() => { - setMounted(true); - return () => setMounted(false); - }, []); - - // Cleanup function to safely remove portal and tippy instance - const cleanup = () => { - if (tippyInstance.current) { - tippyInstance.current[0].destroy(); - tippyInstance.current = null; - } - if (portalRef.current && document.body.contains(portalRef.current)) { - document.body.removeChild(portalRef.current); - portalRef.current = null; - } - }; - - useEffect(() => { - if (!mounted || !containerRef.current || !clientRect) { - return cleanup; - } - - // Create Tippy instance - if (!tippyInstance.current) { - tippyInstance.current = tippy('body', { - getReferenceClientRect: clientRect, - appendTo: document.body, - content: containerRef.current, - showOnCreate: true, - interactive: true, - trigger: 'manual', - placement: 'bottom-start', - arrow: true, - theme: 'mention', - maxWidth: '400px', - offset: [0, 8], - animation: 'scale-subtle', - duration: 150, - }); - } else { - // Update position - tippyInstance.current[0].setProps({ - getReferenceClientRect: clientRect, - }); - } - - return cleanup; - }, [clientRect, mounted]); - - // Cleanup on unmount - useEffect(() => { - return cleanup; - }, []); - - if (!items.length || !mounted) { - cleanup(); - return null; - } - - const portal = createPortal( - <> -
- {items.map((item, index) => ( - - ))} -
- , - document.body - ); - - // Store the portal element reference - if (portal) { - const element = document.querySelector('[data-tippy-root]') as HTMLElement; - if (element && element !== portalRef.current) { - portalRef.current = element; - } - } - - return portal; -}; diff --git a/components/Editor/components/BlockEditor/index.tsx b/components/Editor/components/BlockEditor/index.tsx deleted file mode 100644 index a4c51c39a..000000000 --- a/components/Editor/components/BlockEditor/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './BlockEditor'; diff --git a/components/Editor/components/menus/index.ts b/components/Editor/components/menus/index.ts deleted file mode 100644 index d871045a2..000000000 --- a/components/Editor/components/menus/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './LinkMenu'; -export * from './TextMenu'; -export * from './ContentItemMenu'; diff --git a/components/Editor/extensions/Ai/AiFree.ts b/components/Editor/extensions/Ai/AiFree.ts deleted file mode 100644 index 3c09abd10..000000000 --- a/components/Editor/extensions/Ai/AiFree.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This is a dummy file, to make the project work without the AI extension. -import { Extension } from '@tiptap/core'; - -export type AiStorage = any; -export type Language = any; -export const tryParseToTiptapHTML = (args: any) => args; -export const Ai = Extension.create({ - name: 'aiFree', -}); diff --git a/components/Editor/extensions/AiImage/AiImageFree.ts b/components/Editor/extensions/AiImage/AiImageFree.ts deleted file mode 100644 index 2639be133..000000000 --- a/components/Editor/extensions/AiImage/AiImageFree.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This is a dummy file, to make the project work without the AI extension. -import { Extension } from '@tiptap/core'; - -export const AiImage = Extension.create({ - name: 'aiImageFree', -}); diff --git a/components/Editor/extensions/AiWriter/AiWriterFree.ts b/components/Editor/extensions/AiWriter/AiWriterFree.ts deleted file mode 100644 index 13de611e5..000000000 --- a/components/Editor/extensions/AiWriter/AiWriterFree.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This is a dummy file, to make the project work without the AI extension. -import { Extension } from '@tiptap/core'; - -export const AiWriter = Extension.create({ - name: 'aiWriterFree', -}); diff --git a/components/Editor/extensions/ImageUpload/view/index.tsx b/components/Editor/extensions/ImageUpload/view/index.tsx deleted file mode 100644 index c3d6179dc..000000000 --- a/components/Editor/extensions/ImageUpload/view/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './ImageUpload'; diff --git a/components/Editor/extensions/MultiColumn/menus/index.ts b/components/Editor/extensions/MultiColumn/menus/index.ts deleted file mode 100644 index c39d51bae..000000000 --- a/components/Editor/extensions/MultiColumn/menus/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ColumnsMenu'; diff --git a/components/Editor/extensions/Table/menus/index.tsx b/components/Editor/extensions/Table/menus/index.tsx deleted file mode 100644 index dd08ad7a6..000000000 --- a/components/Editor/extensions/Table/menus/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './TableColumn'; -export * from './TableRow'; diff --git a/components/Editor/hooks/useSidebar.tsx b/components/Editor/hooks/useSidebar.tsx deleted file mode 100644 index abb424865..000000000 --- a/components/Editor/hooks/useSidebar.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useCallback, useMemo, useState } from 'react'; - -export type SidebarState = { - isOpen: boolean; - open: () => void; - close: () => void; - toggle: () => void; -}; - -export const useSidebar = (): SidebarState => { - const [isOpen, setIsOpen] = useState(false); - return useMemo(() => { - return { - isOpen, - open: () => setIsOpen(true), - close: () => setIsOpen(false), - toggle: () => setIsOpen((prev) => !prev), - }; - }, [isOpen]); -}; diff --git a/components/Editor/pages/_document.js b/components/Editor/pages/_document.js deleted file mode 100644 index a1cd1bbcd..000000000 --- a/components/Editor/pages/_document.js +++ /dev/null @@ -1,18 +0,0 @@ -// pages/_document.js -import Document, { Html, Head, Main, NextScript } from 'next/document'; - -class MyDocument extends Document { - render() { - return ( - - {/* Add any custom head elements here */} - -
- - - - ); - } -} - -export default MyDocument; diff --git a/components/Editor/pages/_error.js b/components/Editor/pages/_error.js deleted file mode 100644 index 228112faf..000000000 --- a/components/Editor/pages/_error.js +++ /dev/null @@ -1,20 +0,0 @@ -// pages/_error.js -import React from 'react'; - -function Error({ statusCode }) { - return ( -
-

- {statusCode} - {statusCode === 404 ? 'Page Not Found' : 'Server-side Error'} -

-

Sorry, something went wrong on our end.

-
- ); -} - -Error.getInitialProps = ({ res, err }) => { - const statusCode = res ? res.statusCode : err ? err.statusCode : 404; - return { statusCode }; -}; - -export default Error; diff --git a/components/Feed/ActionButton.tsx b/components/Feed/ActionButton.tsx deleted file mode 100644 index 6522d2d96..000000000 --- a/components/Feed/ActionButton.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { FC } from 'react'; -import { Button } from '@/components/ui/Button'; - -interface ActionButtonProps { - icon: any; - count?: number; - label: string; - tooltip?: string; - onClick?: () => void; - variant?: 'default' | 'ghost'; - className?: string; - active?: boolean; - showLabel?: boolean; -} - -/** - * ActionButton is a reusable component for rendering action buttons in feed items - * It displays an icon with an optional count and tooltip - */ -export const ActionButton: FC = ({ - icon: Icon, - count, - label, - tooltip, - onClick, - variant = 'ghost', - className, - active = false, - showLabel = false, -}) => ( - -); diff --git a/components/Feed/FeedItemMenuButton.tsx b/components/Feed/FeedItemMenuButton.tsx deleted file mode 100644 index 9fc70f728..000000000 --- a/components/Feed/FeedItemMenuButton.tsx +++ /dev/null @@ -1,142 +0,0 @@ -'use client'; - -import { FC, useState, useContext } from 'react'; -import { MoreHorizontal, Flag, Trash2 } from 'lucide-react'; -import { Button } from '@/components/ui/Button'; -import { BaseMenu, BaseMenuItem } from '@/components/ui/form/BaseMenu'; -import { useAuthenticatedAction } from '@/contexts/AuthModalContext'; -import { useFlagModal } from '@/hooks/useFlagging'; -import { FlagContentModal } from '@/components/modals/FlagContentModal'; -import { FeedContentType } from '@/types/feed'; -import { ContentType } from '@/types/work'; -import { ListDetailContext } from '@/components/UserList/lib/user-list'; -import { toast } from 'react-hot-toast'; -import { extractApiErrorMessage } from '@/services/lib/serviceUtils'; - -interface FeedItemMenuButtonProps { - feedContentType: FeedContentType; - relatedDocumentContentType?: ContentType; - relatedDocumentId?: string; - votableEntityId: number; - actionLabel?: string; - relatedDocumentUnifiedDocumentId?: string; -} - -export const FeedItemMenuButton: FC = ({ - feedContentType, - votableEntityId, - relatedDocumentId, - relatedDocumentContentType, - actionLabel = 'Report', - relatedDocumentUnifiedDocumentId, -}) => { - const { executeAuthenticatedAction } = useAuthenticatedAction(); - const [isMenuOpen, setIsMenuOpen] = useState(false); - const { isOpen, contentToFlag, openFlagModal, closeFlagModal } = useFlagModal(); - - const listDetailContext = useContext(ListDetailContext); - - const handleRemoveFromList = async (e?: React.MouseEvent) => { - if (e) { - e.stopPropagation(); - } - - if (!listDetailContext || !relatedDocumentUnifiedDocumentId) { - return; - } - - setIsMenuOpen(false); - - try { - await listDetailContext.onRemoveItem(Number.parseInt(relatedDocumentUnifiedDocumentId)); - } catch (error) { - toast.error(extractApiErrorMessage(error, 'Failed to remove from list')); - } - }; - - const handleReport = (e?: React.MouseEvent) => { - if (e) { - e.stopPropagation(); - } - executeAuthenticatedAction(() => { - // Close the dropdown menu before opening the modal - setIsMenuOpen(false); - - // Map feedContentType to ContentType - let contentType: ContentType; - let commentId: string | undefined; - - if (feedContentType === 'PAPER') { - contentType = 'paper'; - } else if (feedContentType === 'POST' || feedContentType === 'PREREGISTRATION') { - contentType = 'post'; - } else if (feedContentType === 'GRANT') { - contentType = relatedDocumentContentType || 'funding_request'; - } else if (feedContentType === 'BOUNTY' && relatedDocumentContentType) { - contentType = relatedDocumentContentType; - commentId = votableEntityId.toString(); // Use votableEntityId as commentId for bounties - } else if (feedContentType === 'COMMENT') { - contentType = relatedDocumentContentType || 'post'; - commentId = votableEntityId.toString(); - } else { - contentType = 'post'; // Default fallback - } - - openFlagModal( - // For comments and bounties, use relatedDocumentId as the documentId - (feedContentType === 'COMMENT' || feedContentType === 'BOUNTY') && relatedDocumentId - ? relatedDocumentId.toString() - : votableEntityId.toString(), - contentType, - commentId - ); - }); - }; - - return ( - <> - { - e.stopPropagation(); - }} - onClick={(e) => { - e.stopPropagation(); - }} - variant="ghost" - size="sm" - className="flex items-center text-gray-400 hover:text-gray-600" - > - - - } - align="end" - open={isMenuOpen} - onOpenChange={setIsMenuOpen} - > - {listDetailContext && relatedDocumentUnifiedDocumentId && ( - - - Remove from list - - )} - - - - {actionLabel} - - - - {contentToFlag && ( - - )} - - ); -}; diff --git a/components/Feed/lib/documentFeedSortAndFilterConfig.ts b/components/Feed/lib/documentFeedSortAndFilterConfig.ts deleted file mode 100644 index 2da4ed8ba..000000000 --- a/components/Feed/lib/documentFeedSortAndFilterConfig.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type DocumentFeedSortOption = 'hot_score_v2' | 'latest' | 'best'; - -export const SORT_OPTIONS: { label: string; value: DocumentFeedSortOption }[] = [ - { label: 'Best', value: 'best' }, - { label: 'Latest', value: 'latest' }, -]; diff --git a/components/InfoBanner.tsx b/components/InfoBanner.tsx deleted file mode 100644 index f768b95ad..000000000 --- a/components/InfoBanner.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Info } from 'lucide-react'; - -export const InfoBanner: React.FC = () => ( -
-
-
- -
-
-

Get Verified

-

- Verify your academic credentials to unlock additional features and build trust in the - community. -

- -
-
-
-); diff --git a/components/Journal/lib/hubUtils.ts b/components/Journal/lib/hubUtils.ts deleted file mode 100644 index d1f0e7b6f..000000000 --- a/components/Journal/lib/hubUtils.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Utility function to generate slugs -export const slugify = (text: string): string => { - return text - .toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with hyphens - .replace(/[^\w\-]+/g, '') // Remove all non-word chars (except hyphens) - .replace(/\-\-+/g, '-') // Replace multiple hyphens with single hyphen - .trim(); // Trim hyphens from start and end -}; - -// Specific overrides for certain hub names -export const HUB_URL_OVERRIDES: { [key: string]: string | undefined } = { - 'Molecular Biology': 'molecular-biology-1', - 'Pharmaceutical Science': 'pharmaceutical-science-1', - Pharmacology: 'pharmacology-and-toxicology', -}; - -// Function to get the correct hub slug, considering overrides -export const getHubSlug = (name: string): string => { - return HUB_URL_OVERRIDES[name] ?? slugify(name); -}; diff --git a/components/Moderators/utils/auditUtils.ts b/components/Moderators/utils/auditUtils.ts index c440e4e7f..1141a2b05 100644 --- a/components/Moderators/utils/auditUtils.ts +++ b/components/Moderators/utils/auditUtils.ts @@ -1,6 +1,5 @@ import { FlaggedContent } from '@/services/audit.service'; import { buildWorkUrl } from '@/utils/url'; -import { extractTextFromTipTap } from '@/components/Comment/lib/commentContentUtils'; /** * SIMPLIFIED AUDIT UTILS @@ -9,9 +8,6 @@ import { extractTextFromTipTap } from '@/components/Comment/lib/commentContentUt * - buildWorkUrl() from utils/url.ts * - CommentReadOnly component for content rendering * - FeedItemHeader for user/timestamp display - * - * We avoid importing the audit transformation functions due to type incompatibilities - * between @/services/audit.service and @/types/audit FlaggedContent types. */ /** @@ -84,107 +80,6 @@ export const getAuditContentUrl = (entry: FlaggedContent): string | null => { } }; -/** - * Extract text from Quill format content - */ -const extractTextFromQuillContent = (jsonContent: any): string => { - if (jsonContent.ops && Array.isArray(jsonContent.ops)) { - const extractedText = jsonContent.ops - .map((op: any) => op.insert ?? '') - .join('') - .trim(); - - if (extractedText) { - return extractedText; - } - } - return ''; -}; - -/** - * Extract text from TipTap format content - */ -const extractTextFromTipTapDocument = (jsonContent: any): string => { - if (jsonContent.content || jsonContent.type === 'doc') { - const extractedText = extractTextFromTipTap(jsonContent); - if (extractedText) { - return extractedText.trim(); - } - } - return ''; -}; - -/** - * Process JSON comment content and extract text - */ -const processJsonCommentContent = (jsonContent: any): string => { - // Try extracting from Quill format first - const quillText = extractTextFromQuillContent(jsonContent); - if (quillText) { - return quillText; - } - - // Try extracting from TipTap format - const tipTapText = extractTextFromTipTapDocument(jsonContent); - if (tipTapText) { - return tipTapText; - } - - return 'No readable content found'; -}; - -/** - * Handle comment content JSON parsing and extraction - */ -export const handleCommentContentJson = (commentContentJson: string | object): string => { - // Handle special case for removed content - if (typeof commentContentJson === 'string' && commentContentJson.includes('[Comment removed]')) { - return '[Comment removed]'; - } - - try { - const jsonContent = - typeof commentContentJson === 'string' ? JSON.parse(commentContentJson) : commentContentJson; - - return processJsonCommentContent(jsonContent); - } catch (e) { - console.warn('Failed to parse comment_content_json:', e); - return 'Error parsing comment content'; - } -}; - -/** - * Get content from thread document - */ -export const getContentFromThreadDocument = (contentItem: any): string => { - const document = contentItem.thread?.content_object?.unified_document?.documents?.[0]; - if (!document) { - return ''; - } - - return ( - document.renderable_text ?? - document.title ?? - document.abstract ?? - 'No content preview available' - ); -}; - -/** - * Get fallback content from various fields - */ -export const getFallbackContent = (contentItem: any): string => { - return ( - contentItem.content ?? - contentItem.text ?? - contentItem.title ?? - contentItem.description ?? - contentItem.renderable_text ?? - contentItem.abstract ?? - 'No content preview available' - ); -}; - /** * Get content preview text for truncation detection */ @@ -282,11 +177,3 @@ const extractTextFromTipTapArray = (content: any[]): string => { .join('') .trim(); }; - -/** - * Check if content should show truncation based on length - */ -export const shouldShowTruncation = (entry: FlaggedContent, maxLength: number = 400): boolean => { - const textContent = getAuditContentPreview(entry); - return textContent.length > maxLength; -}; diff --git a/components/Notification/NotificationBell.tsx b/components/Notification/NotificationBell.tsx deleted file mode 100644 index eb8c5adc6..000000000 --- a/components/Notification/NotificationBell.tsx +++ /dev/null @@ -1,68 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { useEffect, useState } from 'react'; -import { Icon } from '@/components/ui/icons/Icon'; -import { useNotifications } from '@/contexts/NotificationContext'; -import { useUser } from '@/contexts/UserContext'; -import { useWebSocket } from '@/hooks/useWebSocket'; -import { WS_ROUTES } from '@/services/websocket'; -import { transformNotification } from '@/types/notification'; -import { Tooltip } from '@/components/ui/Tooltip'; -import { formatBadgeCount } from '@/utils/number'; - -interface NotificationBellProps { - filled?: boolean; -} - -export function NotificationBell({ filled }: NotificationBellProps) { - const [unreadCountInternal, setUnreadCountInternal] = useState(0); - const { unreadCount } = useNotifications(); - const { user } = useUser(); - const pathname = usePathname(); - const isNotificationsPage = pathname === '/notifications'; - const href = isNotificationsPage ? '/' : '/notifications'; - - // Connect to WebSocket for real-time notification updates - const { messages } = useWebSocket({ - url: user?.id ? WS_ROUTES.NOTIFICATIONS(user.id) : '', - authRequired: true, - autoConnect: !!user?.id, - global: true, - }); - - // Update unread count when new notifications arrive via websocket - useEffect(() => { - if (messages.length > 0) { - const latestMessage = messages[messages.length - 1]; - try { - transformNotification(latestMessage); - setUnreadCountInternal((prev) => prev + 1); - } catch (error) { - console.error('Error processing notification:', error); - } - } - }, [messages]); - - useEffect(() => { - setUnreadCountInternal((prev) => prev + unreadCount); - }, [unreadCount]); - - return ( - - - - {unreadCountInternal > 0 && ( - - {formatBadgeCount(unreadCountInternal)} - - )} - - - ); -} diff --git a/components/Paper/forms/InstitutionAutocomplete.tsx b/components/Paper/forms/InstitutionAutocomplete.tsx deleted file mode 100644 index 8ca763a24..000000000 --- a/components/Paper/forms/InstitutionAutocomplete.tsx +++ /dev/null @@ -1,160 +0,0 @@ -'use client'; - -import { useCallback } from 'react'; -import { AutocompleteSelect, SelectOption } from '@/components/ui/form/AutocompleteSelect'; -import { Building } from 'lucide-react'; -import { cn } from '@/utils/styles'; -import { SearchService } from '@/services/search.service'; -import type { Institution } from '@/types/institution'; - -interface InstitutionAutocompleteProps { - value: Institution | null; - onChange: (institution: Institution | null) => void; - error?: string; - label?: string; - required?: boolean; -} - -export function InstitutionAutocomplete({ - value, - onChange, - error, - label = 'Institution', - required = false, -}: InstitutionAutocompleteProps) { - const handleSearch = useCallback(async (query: string) => { - try { - const institutions = await SearchService.suggestInstitutions(query); - - return institutions.map((institution) => ({ - value: String(institution.id), - label: institution.name, - data: institution, - })); - } catch (error) { - console.error('Error fetching institutions:', error); - return []; - } - }, []); - - const handleCreateNewInstitution = useCallback(async (name: string) => { - // Create a temporary institution with a generated ID - const newInstitution: Institution = { - id: `new-${Date.now()}`, - name, - }; - - return { - value: String(newInstitution.id), - label: newInstitution.name, - data: newInstitution, - }; - }, []); - - const renderInstitutionOption = ( - option: SelectOption, - { focus, selected }: { selected: boolean; focus: boolean } - ) => { - const institution = option.data as Institution; - - return ( -
  • -
    -
    - {institution.imageThumbnailUrl ? ( -
    - {institution.name} -
    - ) : ( -
    - -
    - )} -
    - -
    -

    {institution.name}

    - - {institution.location && ( -
    - {institution.location} -
    - )} - - {(institution.hIndex || institution.worksCount) && ( -
    - {institution.hIndex && H-Index: {institution.hIndex}} - {institution.worksCount && {institution.worksCount} Works} -
    - )} -
    -
    -
  • - ); - }; - - const renderSelectedInstitution = (option: SelectOption) => { - const institution = option.data as Institution; - - return ( -
    -
    - {institution.imageThumbnailUrl ? ( -
    - {institution.name} -
    - ) : ( -
    - -
    - )} -
    - -
    -

    {institution.name}

    -
    -
    - ); - }; - - return ( - - label={label} - required={required} - value={ - value - ? { - value: String(value.id), - label: value.name, - data: value, - } - : null - } - onChange={(newValue) => { - onChange(newValue?.data || null); - }} - onSearch={handleSearch} - placeholder="Search for an institution..." - debounceMs={300} - error={error} - renderOption={renderInstitutionOption} - renderSelectedValue={renderSelectedInstitution} - allowCreatingNew={true} - onCreateNew={handleCreateNewInstitution} - createNewLabel="Add new institution" - /> - ); -} diff --git a/components/ResearchCoin/AnimatedResearchCoin/index.ts b/components/ResearchCoin/AnimatedResearchCoin/index.ts deleted file mode 100644 index 6245672d8..000000000 --- a/components/ResearchCoin/AnimatedResearchCoin/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AnimatedResearchCoin'; diff --git a/components/changelog/ChangelogLink.tsx b/components/changelog/ChangelogLink.tsx deleted file mode 100644 index 7bfba313a..000000000 --- a/components/changelog/ChangelogLink.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { RadiatingDot } from '@/components/ui/RadiatingDot'; -import { cn } from '@/utils/styles'; -import { useChangelogSeen } from './useChangelogSeen'; - -interface ChangelogLinkProps { - className?: string; -} - -/** - * Renders the Changelog link with a "new entry" radiating-dot indicator that - * persists per-user in localStorage (see useChangelogSeen, shared with the - * sidebar's help menu). - */ -export const ChangelogLink: React.FC = ({ className }) => { - const { hasSeen: hasSeenChangelog, markSeen } = useChangelogSeen(); - - return ( - - {!hasSeenChangelog && } - Changelog - - ); -}; diff --git a/components/modals/ReleaseNotesModal.tsx b/components/modals/ReleaseNotesModal.tsx deleted file mode 100644 index b28736d16..000000000 --- a/components/modals/ReleaseNotesModal.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { Dialog, Transition } from '@headlessui/react'; -import { Fragment } from 'react'; -import { ExternalLink, Rocket, Hammer } from 'lucide-react'; - -interface ReleaseNotesModalProps { - isOpen: boolean; - onClose: () => void; -} - -const ModalHeader = ({ title, onClose }: { title: string; onClose: () => void }) => ( -
    -
    - - {title} - - -
    -
    -); - -export function ReleaseNotesModal({ isOpen, onClose }: ReleaseNotesModalProps) { - return ( - - - -
    - - -
    -
    - - -
    - - -
    - {/* Roadmap Link Section */} -
    -

    - ResearchHub Roadmap -

    - - View Full Roadmap - -
    - - {/* Shipped Section */} -
    -

    - - Shipped -

    -
    -
    -
    02/13/2025
    -
      -
    • Add zero balance and empty transaction states to RSC page
    • -
    • - Integrated funding proposal creation with preregistration template -
    • -
    • Added funding timeline modal for new proposals
    • -
    • Improved organization context handling
    • -
    -
    -
    -
    02/12/2025
    -
      -
    • Working comments (replies, edits, etc.)
    • -
    • Be able to mention users, authors and papers in comment section
    • -
    -
    -
    -
    02/07/2025
    -
      -
    • Improved notebook layout with optimized 3-column grid system
    • -
    • Moved preregistration funding form into the Notebook sidebar
    • -
    • Refactored funding form into reusable component
    • -
    -
    - -
    -
    02/05/2025
    -
      -
    • Implemented email & password authentication
    • -
    • Connected funding creation workflow to backend API
    • -
    • Connected bounty creation to backend API
    • -
    • Connected Fund Research to backend API
    • -
    • Implemented feed based on user follows
    • -
    -
    - -
    -
    01/31/2025
    -
      -
    • Fetch lists of journals, topics, and authors to follow
    • -
    • - Implemented user-specific follow tracking for personalized content -
    • -
    -
    - -
    -
    01/29/25
    -
      -
    • Publish flow as dropdown
    • -
    • Bounty creation via publish menu (UX only)
    • -
    • Improved UX for "Submit your Research" option
    • -
    • - Remove "About", "Explore" and "My ResearchCoin" from left sidebar -
    • -
    • Add "My ResearchCoin" option to topbar next to notifications
    • -
    • Render topics and metadata in work pages sidebar
    • -
    • Render PDF as HTML via pdfjs
    • -
    -
    - -
    -
    01/27/25
    -
      -
    • Fetching user organizations and notes in Lab notebook
    • -
    • Organization switcher component
    • -
    • Display private, public notes in different sections
    • -
    -
    - -
    -
    01/26/25
    -
      -
    • Funding page WIP which fetches content from API dynamically
    • -
    -
    - -
    -
    01/20/25
    -
      -
    • Auto code formatting with eslint and prettier libraries
    • -
    • Funding UX w/optional NFT rewards
    • -
    • Initial Publish workflow
    • -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - ); -} diff --git a/components/modals/RequestFundingModal.tsx b/components/modals/RequestFundingModal.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/components/modals/ShareModalTrigger.tsx b/components/modals/ShareModalTrigger.tsx deleted file mode 100644 index 4455af410..000000000 --- a/components/modals/ShareModalTrigger.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import { Button } from '@/components/ui/Button'; -import Icon from '@/components/ui/icons/Icon'; -import { useShareModalContext } from '@/contexts/ShareContext'; - -export default function ShareModalTrigger() { - const { showShareModal } = useShareModalContext(); - - return ( - <> -
    - -
    - - ); -} diff --git a/components/profile/About/DegreeDropdown.tsx b/components/profile/About/DegreeDropdown.tsx deleted file mode 100644 index 4f8b854c5..000000000 --- a/components/profile/About/DegreeDropdown.tsx +++ /dev/null @@ -1,164 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Dropdown, DropdownItem } from '@/components/ui/form/Dropdown'; -import { ChevronDown } from 'lucide-react'; -import { cn } from '@/utils/styles'; - -// Comprehensive list of degrees from the old codebase -export const degreeOptions = [ - { value: 'HSD', label: 'Highschool Diploma' }, - { value: 'AA', label: "Associate's Degree" }, - { value: 'AA', label: 'Associate of Arts' }, - { value: 'AS', label: 'Asscoiate of Science' }, - { value: 'AAS', label: 'Associate of Arts and Science' }, - { value: 'BA', label: "Bachelor's Degree" }, - { value: "Engineer's Degree", label: "Engineer's Degree" }, - { value: 'Foundation Degree', label: 'Foundation Degree' }, - { value: 'Licentiate Degree', label: 'Licentiate Degree' }, - { value: "Master's Degree", label: "Master's Degree" }, - { value: 'BASc', label: 'Bachelor of Applied Science' }, - { value: 'BArch', label: 'Bachelor of Architecture' }, - { value: 'BA', label: 'Bachelor of Arts' }, - { value: 'BBA', label: 'Bachelor of Business Administration' }, - { value: 'BCom', label: 'Bachelor of Commerce' }, - { value: 'BEd', label: 'Bachelor of Education' }, - { value: 'BE', label: 'Bachelor of Engineering' }, - { value: 'BFA', label: 'Bachelor of Fine Arts' }, - { value: 'LLB', label: 'Bachelor of Laws' }, - { value: 'MBBS', label: 'Bachelor of Medicine, Bachelor of Surgery' }, - { value: 'BPharm', label: 'Bachelor of Pharmacy' }, - { value: 'BS', label: 'Bachelor of Science' }, - { value: 'BTech', label: 'Bachelor of Technology - BTech' }, - { value: 'BVSc', label: 'Bachelor of Veterinary Science' }, - { value: 'MArch', label: 'Master of Architecture' }, - { value: 'MA', label: 'Master of Arts' }, - { value: 'MBA', label: 'Master of Business Administration' }, - { value: 'MCA', label: 'Master of Computer Applications' }, - { value: 'MDiv', label: 'Master of Divinity' }, - { value: 'MEd', label: 'Master of Education' }, - { value: 'MEng', label: 'Master of Engineering' }, - { value: 'MFA', label: 'Master of Fine Arts' }, - { value: 'LLM', label: 'Master of Laws' }, - { value: 'MLIS', label: 'Master of Library & Information Science' }, - { value: 'MPhil', label: 'Master of Philosophy' }, - { value: 'MPA', label: 'Master of Public Administration' }, - { value: 'MPH', label: 'Master of Public Health' }, - { value: 'MS', label: 'Master of Science' }, - { value: 'MSW', label: 'Master of Social Work' }, - { value: 'MTech', label: 'Master of Technology' }, - { value: 'EdD', label: 'Doctor of Education' }, - { value: 'JD', label: 'Doctor of Law' }, - { value: 'MD', label: 'Doctor of Medicine' }, - { value: 'PharmD', label: 'Doctor of Pharmacy' }, - { value: 'PhD', label: 'Doctor of Philosophy' }, - { value: 'DVM', label: 'Doctor of Veterinary Medicine' }, -]; - -// Add some common simplified degree categories at the top for ease of use -export const popularDegreeOptions = [ - { value: "Bachelor's", label: "Bachelor's" }, - { value: "Master's", label: "Master's" }, - { value: 'PhD', label: 'PhD' }, - { value: 'MD', label: 'MD' }, - { value: 'JD', label: 'JD' }, - { value: 'MBA', label: 'MBA' }, - { value: "Associate's", label: "Associate's" }, - { value: 'High School', label: 'High School' }, -]; - -// Combine both arrays, with popular ones first and removing duplicates -export const combinedDegreeOptions = [ - ...popularDegreeOptions, - ...degreeOptions.filter( - (option) => !popularDegreeOptions.some((pop) => pop.value === option.value) - ), -]; - -export interface DegreeOption { - value: string; - label: string; -} - -interface DegreeDropdownProps { - value: DegreeOption | undefined; - onChange: (degree: DegreeOption | undefined) => void; - label?: string; - required?: boolean; - className?: string; -} - -export function DegreeDropdown({ - value, - onChange, - label = 'Degree', - required = false, - className, -}: DegreeDropdownProps) { - const [isOpen, setIsOpen] = useState(false); - - return ( - - {value ? value.label : 'Select Degree'} - - - } - className="max-h-60 overflow-y-auto py-1" - onOpenChange={setIsOpen} - > -
    - onChange(undefined)} - className={!value ? 'bg-gray-100 font-medium' : ''} - > - No degree - - - {/* Show a divider between sections */} -
    -
    Popular
    -
    - - {/* Popular degree options */} - {popularDegreeOptions.map((option) => ( - onChange(option)} - className={value?.value === option.value ? 'bg-gray-100 font-medium' : ''} - > - {option.label} - - ))} - - {/* Show a divider between sections */} -
    -
    All Degrees
    -
    - - {/* All degree options */} - {degreeOptions.map((option) => ( - onChange(option)} - className={value?.value === option.value ? 'bg-gray-100 font-medium' : ''} - > - {option.label} - - ))} -
    -
    - ); -} diff --git a/components/profile/About/EducationCard.tsx b/components/profile/About/EducationCard.tsx deleted file mode 100644 index bb15ac2d3..000000000 --- a/components/profile/About/EducationCard.tsx +++ /dev/null @@ -1,81 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheck, faTrash } from '@fortawesome/free-solid-svg-icons'; -import type { EducationEntry } from './types'; - -interface ProfileAboutEducationCardProps { - education: EducationEntry; - index: number; - onEdit: () => void; - onRemove: () => void; - onSetMain: () => void; -} - -export function ProfileAboutEducationCard({ - education, - index, - onEdit, - onRemove, - onSetMain, -}: ProfileAboutEducationCardProps) { - const [hover, setHover] = useState(false); - - if (!education.summary && !education.name) { - return null; - } - - return ( -
    setHover(true)} - onMouseLeave={() => setHover(false)} - > -
    -
    - {education.summary ? ( -
    {education.summary}
    - ) : ( -
    - {education.degree?.label} {education.major && `in ${education.major}`} - {education.year?.value && ` '${education.year.value.slice(2)}'`} - {education.name && `, ${education.name}`} -
    - )} -
    - -
    - {/* Main checkbox */} -
    { - e.stopPropagation(); - onSetMain(); - }} - title="Set as main education" - > - {education.is_public && ( - - )} -
    - - {/* Remove button */} - -
    -
    -
    - ); -} diff --git a/components/providers/AmplitudeProvider.tsx b/components/providers/AmplitudeProvider.tsx deleted file mode 100644 index e931922e5..000000000 --- a/components/providers/AmplitudeProvider.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import AmplitudeService from '@/services/amplitude.service'; - -const AmplitudeProvider = ({ children }: { children: React.ReactNode }) => { - useEffect(() => { - AmplitudeService.init(); - }, []); - - return <>{children}; -}; - -export default AmplitudeProvider; diff --git a/components/providers/AnalyticsProvider.tsx b/components/providers/AnalyticsProvider.tsx deleted file mode 100644 index a2fe091ac..000000000 --- a/components/providers/AnalyticsProvider.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import AnalyticsService from '@/services/analytics.service'; - -const AnalyticsProvider = ({ children }: { children: React.ReactNode }) => { - useEffect(() => { - AnalyticsService.init(null); - }, []); - - return <>{children}; -}; - -export default AnalyticsProvider; diff --git a/components/skeletons/TopicCardSkeleton.tsx b/components/skeletons/TopicCardSkeleton.tsx deleted file mode 100644 index 2994845ed..000000000 --- a/components/skeletons/TopicCardSkeleton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Skeleton } from '@/components/ui/Skeleton'; - -export function TopicCardSkeleton() { - return ( -
    -
    -
    -
    - {/* Emoji placeholder */} -
    - - {/* Title placeholder */} - -
    - - {/* Follow button placeholder */} - -
    -
    -
    - ); -} diff --git a/components/tooltips/ProfileTooltip.tsx b/components/tooltips/ProfileTooltip.tsx deleted file mode 100644 index 2c43cc31a..000000000 --- a/components/tooltips/ProfileTooltip.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client'; - -import { BadgeCheck } from 'lucide-react'; -import { Popover } from '@headlessui/react'; -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; - -interface ProfileTooltipProps { - type: 'user' | 'organization'; - name: string; - headline?: string; - verified?: boolean; - id?: string; - children: React.ReactNode; -} - -export const ProfileTooltip = ({ - type, - name, - headline, - verified = false, - id = '12345', - children, -}: ProfileTooltipProps) => { - const router = useRouter(); - const [isOpen, setIsOpen] = useState(false); - let hoverTimeout: NodeJS.Timeout; - - const handleMouseEnter = () => { - clearTimeout(hoverTimeout); - setIsOpen(true); - }; - - const handleMouseLeave = () => { - hoverTimeout = setTimeout(() => { - setIsOpen(false); - }, 300); - }; - - const handleViewProfile = () => { - router.push(`/author/${id}`); - setIsOpen(false); - }; - - const badgeElement = verified && ( - - ); - - return ( - -
    - - {children} - {badgeElement} - - - {isOpen && ( - -
    -
    -
    -
    - {name} - {badgeElement} -
    -

    {headline}

    -
    - -
    - - -
    -
    -
    -
    - )} -
    -
    - ); -}; diff --git a/components/ui/Aurora.tsx b/components/ui/Aurora.tsx deleted file mode 100644 index 8fe019f13..000000000 --- a/components/ui/Aurora.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import { useEffect, useRef } from 'react'; -import { Renderer, Program, Mesh, Color, Triangle } from 'ogl'; - -const VERT = `#version 300 es -in vec2 position; -void main() { - gl_Position = vec4(position, 0.0, 1.0); -} -`; - -const FRAG = `#version 300 es -precision highp float; - -uniform float uTime; -uniform float uAmplitude; -uniform vec3 uColorStops[3]; -uniform vec2 uResolution; -uniform float uBlend; - -out vec4 fragColor; - -vec3 permute(vec3 x) { - return mod(((x * 34.0) + 1.0) * x, 289.0); -} - -float snoise(vec2 v){ - const vec4 C = vec4( - 0.211324865405187, 0.366025403784439, - -0.577350269189626, 0.024390243902439 - ); - vec2 i = floor(v + dot(v, C.yy)); - vec2 x0 = v - i + dot(i, C.xx); - vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - i = mod(i, 289.0); - - vec3 p = permute( - permute(i.y + vec3(0.0, i1.y, 1.0)) - + i.x + vec3(0.0, i1.x, 1.0) - ); - - vec3 m = max( - 0.5 - vec3( - dot(x0, x0), - dot(x12.xy, x12.xy), - dot(x12.zw, x12.zw) - ), - 0.0 - ); - m = m * m; - m = m * m; - - vec3 x = 2.0 * fract(p * C.www) - 1.0; - vec3 h = abs(x) - 0.5; - vec3 ox = floor(x + 0.5); - vec3 a0 = x - ox; - m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h); - - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; - return 130.0 * dot(m, g); -} - -struct ColorStop { - vec3 color; - float position; -}; - -#define COLOR_RAMP(colors, factor, finalColor) { \ - int index = 0; \ - for (int i = 0; i < 2; i++) { \ - ColorStop currentColor = colors[i]; \ - bool isInBetween = currentColor.position <= factor; \ - index = int(mix(float(index), float(i), float(isInBetween))); \ - } \ - ColorStop currentColor = colors[index]; \ - ColorStop nextColor = colors[index + 1]; \ - float range = nextColor.position - currentColor.position; \ - float lerpFactor = (factor - currentColor.position) / range; \ - finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \ -} - -void main() { - vec2 uv = gl_FragCoord.xy / uResolution; - - ColorStop colors[3]; - colors[0] = ColorStop(uColorStops[0], 0.0); - colors[1] = ColorStop(uColorStops[1], 0.5); - colors[2] = ColorStop(uColorStops[2], 1.0); - - vec3 rampColor; - COLOR_RAMP(colors, uv.x, rampColor); - - float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude; - height = exp(height); - height = (uv.y * 2.0 - height + 0.2); - float intensity = 0.6 * height; - - float midPoint = 0.20; - float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity); - - vec3 auroraColor = intensity * rampColor; - - fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha); -} -`; - -interface AuroraProps { - colorStops?: string[]; - amplitude?: number; - blend?: number; - time?: number; - speed?: number; -} - -export default function Aurora(props: AuroraProps) { - const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props; - const propsRef = useRef(props); - propsRef.current = props; - - const ctnDom = useRef(null); - - useEffect(() => { - const ctn = ctnDom.current; - if (!ctn) return; - - const renderer = new Renderer({ - alpha: true, - premultipliedAlpha: true, - antialias: true, - }); - const gl = renderer.gl; - gl.clearColor(0, 0, 0, 0); - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.canvas.style.backgroundColor = 'transparent'; - - let program: Program | undefined; - - function resize() { - if (!ctn) return; - const width = ctn.offsetWidth; - const height = ctn.offsetHeight; - renderer.setSize(width, height); - if (program) { - program.uniforms.uResolution.value = [width, height]; - } - } - window.addEventListener('resize', resize); - - const geometry = new Triangle(gl); - if (geometry.attributes.uv) { - delete geometry.attributes.uv; - } - - const colorStopsArray = colorStops.map((hex) => { - const c = new Color(hex); - return [c.r, c.g, c.b]; - }); - - program = new Program(gl, { - vertex: VERT, - fragment: FRAG, - uniforms: { - uTime: { value: 0 }, - uAmplitude: { value: amplitude }, - uColorStops: { value: colorStopsArray }, - uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] }, - uBlend: { value: blend }, - }, - }); - - const mesh = new Mesh(gl, { geometry, program }); - ctn.appendChild(gl.canvas); - - let animateId = 0; - const update = (t: number) => { - animateId = requestAnimationFrame(update); - const { time = t * 0.01, speed = 1.0 } = propsRef.current; - if (program) { - program.uniforms.uTime.value = time * speed * 0.1; - program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0; - program.uniforms.uBlend.value = propsRef.current.blend ?? blend; - const stops = propsRef.current.colorStops ?? colorStops; - program.uniforms.uColorStops.value = stops.map((hex: string) => { - const c = new Color(hex); - return [c.r, c.g, c.b]; - }); - renderer.render({ scene: mesh }); - } - }; - animateId = requestAnimationFrame(update); - - resize(); - - return () => { - cancelAnimationFrame(animateId); - window.removeEventListener('resize', resize); - if (ctn && gl.canvas.parentNode === ctn) { - ctn.removeChild(gl.canvas); - } - gl.getExtension('WEBGL_lose_context')?.loseContext(); - }; - }, [amplitude]); - - return
    ; -} diff --git a/components/ui/AvatarWithBadge.tsx b/components/ui/AvatarWithBadge.tsx deleted file mode 100644 index bae8ac4c7..000000000 --- a/components/ui/AvatarWithBadge.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import { VerifiedBadge } from './VerifiedBadge'; -import { cn } from '@/utils/styles'; - -interface AvatarWithBadgeProps { - src: string; - alt: string; - size?: number; - isVerified?: boolean; - isOrganization?: boolean; - className?: string; -} - -export function AvatarWithBadge({ - src, - alt, - size = 40, - isVerified = false, - isOrganization = false, - className, -}: AvatarWithBadgeProps) { - // Determine badge size based on avatar size - const badgeSize = size <= 24 ? 'xs' : size <= 32 ? 'sm' : size <= 48 ? 'md' : 'lg'; - - return ( -
    - {alt} - - {isVerified && ( -
    - -
    - )} -
    - ); -} diff --git a/components/ui/Card.tsx b/components/ui/Card.tsx deleted file mode 100644 index bae2712c6..000000000 --- a/components/ui/Card.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { cn } from '@/utils/styles'; -import { ReactNode } from 'react'; -import Link from 'next/link'; - -interface CardProps { - children: ReactNode; - className?: string; - href?: string; - onClick?: () => void; -} - -export function Card({ children, className, href, onClick }: CardProps) { - const baseStyles = (interactive: boolean) => - cn( - 'bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden', - interactive && - 'group hover:shadow-md hover:border-indigo-100 transition-all duration-200 cursor-pointer', - className - ); - - const content =
    {children}
    ; - - if (href) { - return ( - - {content} - - ); - } - - if (onClick) { - return ( -
    - {content} -
    - ); - } - - return
    {content}
    ; -} diff --git a/components/ui/CardSwap.tsx b/components/ui/CardSwap.tsx deleted file mode 100644 index ce71efa77..000000000 --- a/components/ui/CardSwap.tsx +++ /dev/null @@ -1,255 +0,0 @@ -'use client'; - -import React, { - Children, - cloneElement, - forwardRef, - isValidElement, - ReactElement, - ReactNode, - RefObject, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { gsap } from 'gsap'; - -export interface CardSwapProps { - width?: number | string; - height?: number | string; - cardDistance?: number; - verticalDistance?: number; - delay?: number; - pauseOnHover?: boolean; - onCardClick?: (idx: number) => void; - skewAmount?: number; - easing?: 'linear' | 'elastic'; - children: ReactNode; -} - -export interface CardProps extends React.HTMLAttributes { - customClass?: string; -} - -export const Card = forwardRef(({ customClass, ...rest }, ref) => { - const [isVisible, setIsVisible] = useState(false); - - useLayoutEffect(() => { - setIsVisible(true); - }, []); - - return ( -
    - ); -}); -Card.displayName = 'Card'; - -type CardRef = RefObject; -interface Slot { - x: number; - y: number; - z: number; - zIndex: number; -} - -const makeSlot = (i: number, distX: number, distY: number, total: number): Slot => ({ - x: i * distX, - y: -i * distY, - z: -i * distX * 1.5, - zIndex: total - i, -}); - -const placeNow = (el: HTMLElement, slot: Slot, skew: number) => - gsap.set(el, { - x: slot.x, - y: slot.y, - z: slot.z, - xPercent: -50, - yPercent: -50, - skewY: skew, - transformOrigin: 'center center', - zIndex: slot.zIndex, - force3D: true, - }); - -const CardSwap: React.FC = ({ - width = 500, - height = 400, - cardDistance = 60, - verticalDistance = 70, - delay = 5000, - pauseOnHover = false, - onCardClick, - skewAmount = 6, - easing = 'elastic', - children, -}) => { - const config = - easing === 'elastic' - ? { - ease: 'elastic.out(0.6,0.9)', - durDrop: 2, - durMove: 2, - durReturn: 2, - promoteOverlap: 0.9, - returnDelay: 0.05, - } - : { - ease: 'power1.inOut', - durDrop: 0.8, - durMove: 0.8, - durReturn: 0.8, - promoteOverlap: 0.45, - returnDelay: 0.2, - }; - - const childArr = useMemo( - () => Children.toArray(children) as ReactElement[], - [children] - ); - const refs = useMemo( - () => childArr.map(() => React.createRef()), - [childArr.length] - ); - - const order = useRef(Array.from({ length: childArr.length }, (_, i) => i)); - - const tlRef = useRef(null); - const intervalRef = useRef(undefined); - const container = useRef(null); - - useEffect(() => { - const total = refs.length; - refs.forEach((r, i) => - placeNow(r.current!, makeSlot(i, cardDistance, verticalDistance, total), skewAmount) - ); - - const swap = () => { - if (order.current.length < 2) return; - - const [front, ...rest] = order.current; - const elFront = refs[front].current!; - const tl = gsap.timeline(); - tlRef.current = tl; - - tl.to(elFront, { - y: '+=500', - duration: config.durDrop, - ease: config.ease, - }); - - tl.addLabel('promote', `-=${config.durDrop * config.promoteOverlap}`); - rest.forEach((idx, i) => { - const el = refs[idx].current!; - const slot = makeSlot(i, cardDistance, verticalDistance, refs.length); - tl.set(el, { zIndex: slot.zIndex }, 'promote'); - tl.to( - el, - { - x: slot.x, - y: slot.y, - z: slot.z, - duration: config.durMove, - ease: config.ease, - }, - `promote+=${i * 0.15}` - ); - }); - - const backSlot = makeSlot(refs.length - 1, cardDistance, verticalDistance, refs.length); - tl.addLabel('return', `promote+=${config.durMove * config.returnDelay}`); - tl.call( - () => { - gsap.set(elFront, { zIndex: backSlot.zIndex }); - }, - undefined, - 'return' - ); - tl.set(elFront, { x: backSlot.x, z: backSlot.z }, 'return'); - tl.to( - elFront, - { - y: backSlot.y, - duration: config.durReturn, - ease: config.ease, - }, - 'return' - ); - - tl.call(() => { - order.current = [...rest, front]; - }); - }; - - swap(); - intervalRef.current = window.setInterval(swap, delay); - - if (pauseOnHover) { - const node = container.current!; - const pause = () => { - tlRef.current?.pause(); - clearInterval(intervalRef.current); - }; - const resume = () => { - tlRef.current?.play(); - intervalRef.current = window.setInterval(swap, delay); - }; - node.addEventListener('mouseenter', pause); - node.addEventListener('mouseleave', resume); - return () => { - node.removeEventListener('mouseenter', pause); - node.removeEventListener('mouseleave', resume); - clearInterval(intervalRef.current); - }; - } - return () => clearInterval(intervalRef.current); - }, [cardDistance, verticalDistance, delay, pauseOnHover, skewAmount, easing]); - - const rendered = childArr.map((child, i) => - isValidElement(child) - ? cloneElement(child, { - key: i, - ref: refs[i], - style: { width, /*height,*/ ...(child.props.style ?? {}) }, - onClick: (e) => { - child.props.onClick?.(e as React.MouseEvent); - onCardClick?.(i); - }, - } as CardProps & React.RefAttributes) - : child - ); - - return ( -
    - {rendered} -
    - ); -}; - -export default CardSwap; diff --git a/components/ui/JournalStatusBadge.tsx b/components/ui/JournalStatusBadge.tsx deleted file mode 100644 index 3d55cba0d..000000000 --- a/components/ui/JournalStatusBadge.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client'; - -import { FC } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCircleCheck } from '@fortawesome/pro-solid-svg-icons'; -import { Tooltip } from '@/components/ui/Tooltip'; -import { cn } from '@/utils/styles'; - -interface JournalStatusBadgeProps { - status: 'published' | 'in-review'; - className?: string; - showTooltip?: boolean; -} - -export const JournalStatusBadge: FC = ({ - status, - className = '', - showTooltip = true, -}) => { - // In Review badge with yellow dot - if (status === 'in-review') { - const badge = ( - - - In Review - - ); - - if (!showTooltip) { - return badge; - } - - return ( - -
    - -
    -
    Paper is currently undergoing peer review.
    -
    - } - position="top" - width="w-[360px]" - > - {badge} - - ); - } - - // Published badge with Font Awesome solid check icon - if (status === 'published') { - const badge = ( - - - Published - - ); - - if (!showTooltip) { - return badge; - } - - return ( - -
    - -
    -
    Papers that have undergone peer review and been formally published.
    -
    - } - position="top" - width="w-[360px]" - > - {badge} - - ); - } - - // Should never reach here due to type constraints - return null; -}; diff --git a/components/ui/PaperBadge.tsx b/components/ui/PaperBadge.tsx deleted file mode 100644 index 532b1ba79..000000000 --- a/components/ui/PaperBadge.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ContentTypeBadge } from '@/components/ui/ContentTypeBadge'; - -interface PaperBadgeProps { - className?: string; -} - -export const PaperBadge = ({ className = '' }: PaperBadgeProps) => { - return ; -}; diff --git a/components/ui/ProfileTooltip.tsx b/components/ui/ProfileTooltip.tsx deleted file mode 100644 index 2dd63bafa..000000000 --- a/components/ui/ProfileTooltip.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client'; - -import { FC } from 'react'; -import { Tooltip } from './Tooltip'; -import { BadgeCheck } from 'lucide-react'; - -interface ProfileTooltipProps { - type: 'user' | 'organization'; - name: string; - headline?: string; - verified?: boolean; - children: React.ReactNode; -} - -export const ProfileTooltip: FC = ({ - type, - name, - headline, - verified, - children, -}) => { - return ( - -
    - {name} - {verified && } -
    - {headline && {headline}} - {type} -
    - } - > - {children} - - ); -}; diff --git a/components/ui/SplitText/SplitText.tsx b/components/ui/SplitText/SplitText.tsx deleted file mode 100644 index 689684d40..000000000 --- a/components/ui/SplitText/SplitText.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import React, { useRef, useEffect, useState, useId } from 'react'; -import { gsap } from 'gsap'; -import { ScrollTrigger } from 'gsap/ScrollTrigger'; - -// Register only ScrollTrigger as SplitText is a premium plugin -if (typeof window !== 'undefined') { - gsap.registerPlugin(ScrollTrigger); -} - -// Global store to track which animations have run -const animatedElements = new Set(); - -export interface SplitTextProps { - text: string; - className?: string; - delay?: number; - duration?: number; - ease?: string | ((t: number) => number); - splitType?: 'chars' | 'words' | 'lines'; - from?: gsap.TweenVars; - to?: gsap.TweenVars; - threshold?: number; - rootMargin?: string; - textAlign?: React.CSSProperties['textAlign']; - onLetterAnimationComplete?: () => void; - id?: string; // Optional custom ID -} - -// Helper function to split text manually -const splitTextContent = (text: string, splitType: 'chars' | 'words' | 'lines') => { - if (splitType === 'words') { - return text.split(' ').map((word, i) => ( - - {word}  - - )); - } else if (splitType === 'chars') { - // Convert text to array of characters while preserving spaces - const characters = []; - for (let i = 0; i < text.length; i++) { - const char = text[i]; - if (char === ' ') { - // Add a non-breaking space with proper styling - characters.push( - -   - - ); - } else { - characters.push( - - {char} - - ); - } - } - return characters; - } - // Default to lines (though this is simplified) - return text.split('\n').map((line, i) => ( - - {line} - - )); -}; - -const SplitText: React.FC = ({ - text, - className = '', - delay = 100, - duration = 0.6, - ease = 'power3.out', - splitType = 'chars', - from = { opacity: 0, y: 40 }, - to = { opacity: 1, y: 0 }, - threshold = 0.1, - rootMargin = '-100px', - textAlign = 'center', - onLetterAnimationComplete, - id: customId, -}) => { - const ref = useRef(null); - const generatedId = useId(); - const uniqueId = customId || `split-text-${generatedId}`; - const [isAnimated, setIsAnimated] = useState(animatedElements.has(uniqueId)); - - // Render the split text - const splitContent = splitTextContent(text, splitType); - - useEffect(() => { - // Only run on client-side - if (typeof window === 'undefined') return; - - const el = ref.current; - if (!el || animatedElements.has(uniqueId)) { - // If already animated, just set the final state - if (el && animatedElements.has(uniqueId)) { - const elements = Array.from(el.children); - gsap.set(elements, { ...to }); - } - return; - } - - // Get all the split elements - const elements = Array.from(el.children); - if (!elements.length) return; - - // Set initial state - gsap.set(elements, { ...from }); - - // Create animation timeline - const tl = gsap.timeline({ - scrollTrigger: { - trigger: el, - start: `top ${(1 - threshold) * 100}%`, - toggleActions: 'play none none none', - once: true, - }, - onComplete: () => { - animatedElements.add(uniqueId); - setIsAnimated(true); - onLetterAnimationComplete?.(); - }, - }); - - // Animate each element - tl.to(elements, { - ...to, - duration, - ease, - stagger: delay / 1000, - force3D: true, - }); - - return () => { - tl.kill(); - if (ScrollTrigger) { - const triggers = ScrollTrigger.getAll().filter((t) => t.vars.trigger === el); - triggers.forEach((t) => t.kill()); - } - gsap.killTweensOf(elements); - }; - }, [ - uniqueId, - text, - delay, - duration, - ease, - splitType, - from, - to, - threshold, - rootMargin, - onLetterAnimationComplete, - ]); - - return ( -
    - {splitContent} -
    - ); -}; - -// Force a new instance when key changes -export default React.memo(SplitText, (prevProps, nextProps) => { - // Only re-render if these specific props change - return ( - prevProps.text === nextProps.text && - prevProps.id === nextProps.id && - prevProps.className === nextProps.className - ); -}); diff --git a/components/ui/SpotlightCard.tsx b/components/ui/SpotlightCard.tsx deleted file mode 100644 index ad636dfce..000000000 --- a/components/ui/SpotlightCard.tsx +++ /dev/null @@ -1,72 +0,0 @@ -'use client'; - -import React, { useRef, useState } from 'react'; - -interface Position { - x: number; - y: number; -} - -interface SpotlightCardProps extends React.PropsWithChildren { - className?: string; - spotlightColor?: `rgba(${number}, ${number}, ${number}, ${number})`; -} - -const SpotlightCard: React.FC = ({ - children, - className = '', - spotlightColor = 'rgba(255, 255, 255, 0.25)', -}) => { - const divRef = useRef(null); - const [isFocused, setIsFocused] = useState(false); - const [position, setPosition] = useState({ x: 0, y: 0 }); - const [opacity, setOpacity] = useState(0); - - const handleMouseMove: React.MouseEventHandler = (e) => { - if (!divRef.current || isFocused) return; - - const rect = divRef.current.getBoundingClientRect(); - setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }); - }; - - const handleFocus = () => { - setIsFocused(true); - setOpacity(0.6); - }; - - const handleBlur = () => { - setIsFocused(false); - setOpacity(0); - }; - - const handleMouseEnter = () => { - setOpacity(0.6); - }; - - const handleMouseLeave = () => { - setOpacity(0); - }; - - return ( -
    -
    - {children} -
    - ); -}; - -export default SpotlightCard; diff --git a/components/ui/StatusBadge.tsx b/components/ui/StatusBadge.tsx deleted file mode 100644 index adf6c6bcf..000000000 --- a/components/ui/StatusBadge.tsx +++ /dev/null @@ -1,90 +0,0 @@ -'use client'; - -import { FC } from 'react'; -import { cn } from '@/utils/styles'; -import { RadiatingDot } from './RadiatingDot'; -import { Badge } from './Badge'; - -interface StatusBadgeProps { - status: 'open' | 'closed' | 'pending' | 'completed' | 'expiring'; - className?: string; - size?: 'xs' | 'sm' | 'md'; -} - -interface StatusConfig { - dotColor: string; - textColor: string; - bgColor: string; - borderColor: string; - label: string; -} - -export const StatusBadge: FC = ({ status, className, size = 'sm' }) => { - const sizeClasses = { - xs: 'text-xs gap-1 py-1 px-2', - sm: 'text-sm gap-1.5 py-1.5 px-3', - md: 'text-base gap-2 py-2 px-4', - }; - - const statusConfig: Record = { - open: { - dotColor: 'bg-blue-500', - textColor: 'text-blue-700', - bgColor: 'bg-blue-50', - borderColor: 'border-blue-100', - label: 'Open', - }, - expiring: { - dotColor: 'bg-orange-500', - textColor: 'text-orange-700', - bgColor: 'bg-orange-50', - borderColor: 'border-orange-200', - label: 'Expiring Soon', - }, - closed: { - dotColor: 'bg-gray-500', - textColor: 'text-gray-700', - bgColor: 'bg-gray-50', - borderColor: 'border-gray-200', - label: 'Closed', - }, - pending: { - dotColor: 'bg-yellow-500', - textColor: 'text-yellow-700', - bgColor: 'bg-yellow-50', - borderColor: 'border-yellow-100', - label: 'Pending', - }, - completed: { - dotColor: 'bg-green-500', - textColor: 'text-green-700', - bgColor: 'bg-green-50', - borderColor: 'border-green-100', - label: 'Completed', - }, - }; - - const config = statusConfig[status]; - - const badgeSize = size === 'xs' ? 'sm' : size === 'sm' ? 'default' : 'lg'; - - return ( - - - {config.label} - - ); -}; diff --git a/components/ui/VoteButton.tsx b/components/ui/VoteButton.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/components/ui/examples/RSCBadgeExamples.tsx b/components/ui/examples/RSCBadgeExamples.tsx deleted file mode 100644 index 3047d7afc..000000000 --- a/components/ui/examples/RSCBadgeExamples.tsx +++ /dev/null @@ -1,100 +0,0 @@ -'use client'; - -import React from 'react'; -import { CurrencyBadge } from '../CurrencyBadge'; - -export const CurrencyBadgeExamples = () => { - return ( -
    -

    CurrencyBadge Component Examples

    -
    -
    -

    Basic Usage

    -
    -
    -

    RSC (default)

    - -
    -
    -

    USD Currency

    - -
    -
    -

    Shortened RSC

    - -
    -
    -

    Shortened USD

    - -
    -
    -
    - -
    -

    Sizes

    -
    - - - - - -
    -
    - -
    -

    Variants

    -
    - - - - - - - - -
    -
    - -
    -

    Customization

    -
    - - - - - - - - -
    -
    - -
    -

    Tooltip Behavior

    -
    -
    -

    RSC (shows USD in tooltip)

    - -
    -
    -

    USD (shows RSC in tooltip)

    - -
    -
    -

    Tooltip Disabled

    - -
    -
    -

    USD - Tooltip Disabled

    - -
    -
    -
    -
    -
    - ); -}; diff --git a/components/ui/icons/ClaimPaperIcon.tsx b/components/ui/icons/ClaimPaperIcon.tsx deleted file mode 100644 index ab2253ab2..000000000 --- a/components/ui/icons/ClaimPaperIcon.tsx +++ /dev/null @@ -1,40 +0,0 @@ -'use client'; - -import { FileText, BadgeCheck } from 'lucide-react'; -import { cn } from '@/utils/styles'; - -interface ClaimPaperIconProps { - /** - * Size in pixels. Component maintains 1:1 aspect ratio - */ - size?: number; - /** - * Optional CSS class name - */ - className?: string; - /** - * Optional click handler - */ - onClick?: (event: React.MouseEvent) => void; -} - -export function ClaimPaperIcon({ size = 24, className, onClick }: ClaimPaperIconProps) { - const badgeSize = Math.max(12, size * 0.65); - - return ( -
    - -
    - -
    -
    - ); -} diff --git a/components/ui/icons/ContributeRSC.tsx b/components/ui/icons/ContributeRSC.tsx deleted file mode 100644 index dfdc8591f..000000000 --- a/components/ui/icons/ContributeRSC.tsx +++ /dev/null @@ -1,81 +0,0 @@ -'use client'; - -import { cn } from '@/utils/styles'; -import { ResearchCoinIcon } from './ResearchCoinIcon'; -import type { AuthorProfile } from '@/types/authorProfile'; -import { formatRSC } from '@/utils/number'; -import { Button } from '../Button'; -import { ContributorsButton } from '../ContributorsButton'; -import type { ContributionTotals } from '@/types/funding'; - -interface ContributeRSCProps { - /** - * Size in pixels. Component maintains 1:1 aspect ratio - */ - size?: number; - /** - * Primary color - * @default '#4D525D' - */ - iconColor?: string; - /** - * Text color - * @default '#101827' - */ - textColor?: string; - /** - * Optional CSS class name - */ - className?: string; - /** - * Optional click handler - */ - onClick?: (event: React.MouseEvent) => void; - /** - * Amount of RSC earned - */ - amount?: number; - /** - * Contributors to show in avatar stack - */ - contributors?: Array<{ - profile: AuthorProfile; - amounts: ContributionTotals; - }>; -} - -export function ContributeRSC({ - size = 24, - iconColor = '#4D525D', - textColor = '#101827', - className, - onClick, - amount, - contributors = [], -}: ContributeRSCProps) { - return ( -
    - - - {contributors.length > 0 && ( - onClick({} as React.MouseEvent) : undefined} - /> - )} -
    - ); -} diff --git a/components/ui/icons/GrantIcon.tsx b/components/ui/icons/GrantIcon.tsx deleted file mode 100644 index 8ccadd98d..000000000 --- a/components/ui/icons/GrantIcon.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client'; - -import { cn } from '@/utils/styles'; -import { ResearchCoinIcon } from './ResearchCoinIcon'; -import { HandHelping } from 'lucide-react'; - -interface GrantIconProps { - /** - * Size in pixels. Component maintains 1:1 aspect ratio - */ - size?: number; - /** - * Primary color of the icon - */ - color?: string; - /** - * Optional CSS class name - */ - className?: string; - /** - * Optional click handler - */ - onClick?: (event: React.MouseEvent) => void; -} - -export function GrantIcon({ - size = 24, - color = '#3971ff', // Default to primary-500 - className, - onClick, -}: GrantIconProps) { - const coinSize = size * 0.55; - - return ( -
    - {/* Create a container with padding to accommodate the coin */} -
    - {/* Hand in background */} - -
    - - {/* ResearchCoin positioned over the palm */} -
    - -
    -
    - ); -} diff --git a/components/ui/icons/IconExamples.tsx b/components/ui/icons/IconExamples.tsx deleted file mode 100644 index 3bddf5b9b..000000000 --- a/components/ui/icons/IconExamples.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { FC } from 'react'; -import Icon from './Icon'; - -/** - * Component to showcase the newly added icons - */ -export const IconExamples: FC = () => { - return ( -
    -
    -

    Analytics Icons

    -
    -
    - - upChart1 -
    -
    - - upChart2 -
    -
    - - gauge -
    -
    -
    - -
    -

    Solid Icons

    -
    -
    - - solidHand -
    -
    - - solidNotebook -
    -
    - - solidCoin -
    -
    - - solidBook -
    -
    -
    - -
    -

    Bold RSC Icons

    -
    -
    - - rscBold1 -
    -
    - - rscBold2 -
    -
    - - Blue rscBold1 -
    -
    - - Gold rscBold2 -
    -
    -
    - -
    -

    Interactive Icons

    -
    -
    - - settings -
    -
    - - comment -
    -
    - - upvote -
    -
    - - report -
    -
    - - lightening -
    -
    - - solid -
    -
    -
    - -
    -

    Colorized Icons Example

    -
    -
    - - Blue Settings -
    -
    - - Gold Upvote -
    -
    - - Gold Lightening -
    -
    - - Blue solidBook -
    -
    -
    -
    - ); -}; - -export default IconExamples; diff --git a/components/ui/icons/RSCIcon.tsx b/components/ui/icons/RSCIcon.tsx deleted file mode 100644 index db131fd40..000000000 --- a/components/ui/icons/RSCIcon.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { FC } from 'react'; -import Icon from './Icon'; - -interface RSCIconProps { - /** - * The size of the icon in pixels - * @default 24 - */ - size?: number; - - /** - * Additional CSS classes - */ - className?: string; - - /** - * Color to apply to the RSC icon - * @default undefined (uses original colors) - */ - color?: string; -} - -/** - * RSC Icon component with color customization support - */ -export const RSCIcon: FC = ({ size = 24, className = '', color }) => { - return ; -}; - -export default RSCIcon; diff --git a/components/ui/icons/WalletIcon.tsx b/components/ui/icons/WalletIcon.tsx deleted file mode 100644 index a80860751..000000000 --- a/components/ui/icons/WalletIcon.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { FC } from 'react'; -import Image from 'next/image'; - -export type WalletVariant = 'wallet1' | 'wallet2' | 'wallet3' | 'wallet5' | 'wallet6' | 'default'; - -interface WalletIconProps { - /** - * The size of the icon in pixels - * @default 24 - */ - size?: number; - - /** - * The color of the icon for the default variant - * @default "#0C0C0C" - */ - color?: string; - - /** - * Additional CSS classes - */ - className?: string; - - /** - * The wallet variant to display - * @default "default" - */ - variant?: WalletVariant; -} - -export const WalletIcon: FC = ({ - size = 24, - color = '#0C0C0C', - className = '', - variant = 'default', -}) => { - // If using an SVG variant from public/icons - if (variant !== 'default') { - return ( - {`${variant} - ); - } - - // Default wallet icon as inline SVG - return ( - - - - - - ); -}; - -export default WalletIcon; diff --git a/components/ui/nav/Pill.tsx b/components/ui/nav/Pill.tsx deleted file mode 100644 index 119a7420b..000000000 --- a/components/ui/nav/Pill.tsx +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import { FC, ButtonHTMLAttributes } from 'react'; -import { cn } from '@/utils/styles'; -import { PillSize, pillSizeClasses } from './pillSize'; - -export interface PillProps extends ButtonHTMLAttributes { - active?: boolean; - size?: PillSize; -} - -export const Pill: FC = ({ - active = false, - size = 'sm', - className, - children, - ...props -}) => { - return ( - - ); -}; diff --git a/components/ui/nav/PillBinary.tsx b/components/ui/nav/PillBinary.tsx deleted file mode 100644 index c6f8470da..000000000 --- a/components/ui/nav/PillBinary.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; - -import { FC } from 'react'; -import { cn } from '@/utils/styles'; -import { PillSize, pillSizeClasses } from './pillSize'; - -export interface PillBinaryProps { - label: string; - active: boolean; - onChange: (active: boolean) => void; - size?: PillSize; - className?: string; -} - -export const PillBinary: FC = ({ - label, - active, - onChange, - size = 'sm', - className, -}) => { - return ( - - ); -}; diff --git a/components/ui/nav/pillSize.ts b/components/ui/nav/pillSize.ts deleted file mode 100644 index 74583f6de..000000000 --- a/components/ui/nav/pillSize.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type PillSize = 'xs' | 'sm' | 'md' | 'lg'; - -export const pillSizeClasses: Record = { - xs: 'gap-1 px-2.5 py-1 text-xs', - sm: 'gap-1.5 px-3 py-1.5 text-sm', - md: 'gap-1.5 px-4 py-2 text-sm', - lg: 'gap-2 px-5 py-2.5 text-base', -}; diff --git a/contexts/SidebarContext.tsx b/contexts/SidebarContext.tsx deleted file mode 100644 index c58e39b04..000000000 --- a/contexts/SidebarContext.tsx +++ /dev/null @@ -1,70 +0,0 @@ -'use client'; - -import { createContext, useContext, useState, ReactNode, useCallback, useMemo } from 'react'; - -interface SidebarContextType { - isLeftSidebarOpen: boolean; - isRightSidebarOpen: boolean; - openLeftSidebar: () => void; - closeLeftSidebar: () => void; - toggleLeftSidebar: () => void; - openRightSidebar: () => void; - closeRightSidebar: () => void; - toggleRightSidebar: () => void; - closeBothSidebars: () => void; -} - -const SidebarContext = createContext(undefined); - -export function SidebarProvider({ children }: { children: ReactNode }) { - const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(false); - const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(false); - - const openLeftSidebar = useCallback(() => setIsLeftSidebarOpen(true), []); - const closeLeftSidebar = useCallback(() => setIsLeftSidebarOpen(false), []); - const toggleLeftSidebar = useCallback(() => setIsLeftSidebarOpen((prev) => !prev), []); - - const openRightSidebar = useCallback(() => setIsRightSidebarOpen(true), []); - const closeRightSidebar = useCallback(() => setIsRightSidebarOpen(false), []); - const toggleRightSidebar = useCallback(() => setIsRightSidebarOpen((prev) => !prev), []); - - const closeBothSidebars = useCallback(() => { - setIsLeftSidebarOpen(false); - setIsRightSidebarOpen(false); - }, []); - - const value = useMemo( - () => ({ - isLeftSidebarOpen, - isRightSidebarOpen, - openLeftSidebar, - closeLeftSidebar, - toggleLeftSidebar, - openRightSidebar, - closeRightSidebar, - toggleRightSidebar, - closeBothSidebars, - }), - [ - isLeftSidebarOpen, - isRightSidebarOpen, - openLeftSidebar, - closeLeftSidebar, - toggleLeftSidebar, - openRightSidebar, - closeRightSidebar, - toggleRightSidebar, - closeBothSidebars, - ] - ); - - return {children}; -} - -export function useSidebar() { - const context = useContext(SidebarContext); - if (context === undefined) { - throw new Error('useSidebar must be used within a SidebarProvider'); - } - return context; -} diff --git a/hooks/useOutreachReplyTo.ts b/hooks/useOutreachReplyTo.ts deleted file mode 100644 index af9bef0c0..000000000 --- a/hooks/useOutreachReplyTo.ts +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; - -import { useEffect, useState, type Dispatch, type SetStateAction } from 'react'; -import { - readOutreachReplyToFromStorage, - writeOutreachReplyToToStorage, -} from '@/app/expert-finder/lib/outreachReplyToStorage'; - -export interface UseOutreachReplyToReturn { - replyTo: string; - setReplyTo: Dispatch>; -} - -/** Shared Reply-To field for expert-finder send/preview (list + detail). */ -export function useOutreachReplyTo(): UseOutreachReplyToReturn { - const [replyTo, setReplyTo] = useState(''); - const [storageReady, setStorageReady] = useState(false); - - useEffect(() => { - setReplyTo(readOutreachReplyToFromStorage()); - setStorageReady(true); - }, []); - - useEffect(() => { - if (!storageReady) return; - writeOutreachReplyToToStorage(replyTo); - }, [replyTo, storageReady]); - - return { replyTo, setReplyTo }; -} diff --git a/hooks/useTip.ts b/hooks/useTip.ts index 30e28a53e..5696a0fc7 100644 --- a/hooks/useTip.ts +++ b/hooks/useTip.ts @@ -2,10 +2,6 @@ import { useState, useCallback } from 'react'; import { toast } from 'react-hot-toast'; -// Remove useSession import if no longer needed directly in this hook -// import { useSession } from 'next-auth/react'; -// Remove uuid import, it will be generated in the service -// import { v4 as uuidv4 } from 'uuid'; import { ApiError } from '@/services/types'; import { TransactionService } from '@/services/transaction.service'; import { FeedContentType } from '@/types/feed'; diff --git a/lib/dateUtils.ts b/lib/dateUtils.ts deleted file mode 100644 index fd71dfde2..000000000 --- a/lib/dateUtils.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { format, subDays } from 'date-fns'; - -export const DATE_FORMAT = 'yyyy-MM-dd'; - -export const formatDate = (date: Date): string => { - return format(date, DATE_FORMAT); -}; - -export const getLastWeekRange = (): { start: Date; end: Date } => { - const end = new Date(); - const start = subDays(end, 7); - return { start, end }; -}; - -export const getLastMonthRange = (): { start: Date; end: Date } => { - const end = new Date(); - const start = subDays(end, 30); - return { start, end }; -}; - -export const getLastYearRange = (): { start: Date; end: Date } => { - // New implementation: last 365 days relative to today - const end = new Date(); - const start = subDays(end, 365); - return { start, end }; -}; diff --git a/next.config.js b/next.config.js index 6d700ce87..5c95b1cfe 100644 --- a/next.config.js +++ b/next.config.js @@ -230,9 +230,6 @@ const nextConfig = { }, }, compress: true, - // compiler: { - // removeConsole: process.env.VERCEL_ENV === 'production', - // }, turbopack: { resolveAlias: { '@': __dirname, diff --git a/package-lock.json b/package-lock.json index 4d11eb024..cb6dfcba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,14 +78,13 @@ "@tiptap/react": "3.29.2", "@tiptap/starter-kit": "3.29.2", "@tiptap/suggestion": "3.29.2", - "@types/qrcode.react": "^1.0.5", - "@types/react-datepicker": "^6.2.0", "@types/sanitize-html": "^2.13.0", "@vercel/analytics": "^1.5.0", "cal-sans": "^1.0.1", "chart.js": "^4.4.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^3.6.0", "dayjs": "^1.11.13", "framer-motion": "^12.23.0", "gsap": "^3.13.0", @@ -98,7 +97,6 @@ "markdown-it": "^14.3.0", "next": "^16.3.0", "next-auth": "^4.24.11", - "ogl": "^1.0.11", "pdfjs-dist": "^4.10.38", "persona-react": "6.3.1", "qrcode.react": "^4.2.0", @@ -114,27 +112,22 @@ "react-intersection-observer": "^9.16.0", "sanitize-html": "^2.14.0", "tailwind-merge": "^2.5.5", - "tailwindcss-animate": "^1.0.7", "tippy.js": "^6.3.7", "uuid": "^14.0.0", "zod": "^3.24.2" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.6", "@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/pro-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", "@playwright/test": "^1.62.1", - "@tailwindcss/typography": "^0.5.16", "@types/js-cookie": "^3.0.6", "@types/jsonwebtoken": "^9.0.7", "@types/lodash-es": "^4.17.12", "@types/markdown-it": "^14.1.2", "@types/node": "^20", "@types/react": "^19.2.18", - "@types/react-avatar-editor": "^13.0.4", "@types/react-dom": "^19.2.4", - "autoprefixer": "^10.4.20", "eslint": "^9.39.5", "eslint-config-next": "^16.3.0", "eslint-config-prettier": "^10.1.8", @@ -156,6 +149,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2078,6 +2072,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2095,6 +2090,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2112,6 +2108,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2129,6 +2126,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2146,6 +2144,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2163,6 +2162,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2180,6 +2180,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2197,6 +2198,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2214,6 +2216,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2231,6 +2234,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2248,6 +2252,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2265,6 +2270,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2282,6 +2288,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2299,6 +2306,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2316,6 +2324,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2333,6 +2342,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2350,6 +2360,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2367,6 +2378,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2384,6 +2396,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2401,6 +2414,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2418,6 +2432,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2435,6 +2450,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2452,6 +2468,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2469,6 +2486,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2486,6 +2504,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2503,6 +2522,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3659,6 +3679,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3680,6 +3701,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -3689,12 +3711,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -4883,6 +4907,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4896,6 +4921,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4905,6 +4931,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -7246,19 +7273,6 @@ "tslib": "^2.8.0" } }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - } - }, "node_modules/@tanstack/query-core": { "version": "5.90.20", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", @@ -8396,15 +8410,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/qrcode.react": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.5.tgz", - "integrity": "sha512-BghPtnlwvrvq8QkGa1H25YnN+5OIgCKFuQruncGWLGJYOzeSKiix/4+B9BtfKF2wf5ja8yfyWYA3OXju995G8w==", - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react": { "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", @@ -8414,42 +8419,6 @@ "csstype": "^3.2.2" } }, - "node_modules/@types/react-avatar-editor": { - "version": "13.0.4", - "resolved": "https://registry.npmjs.org/@types/react-avatar-editor/-/react-avatar-editor-13.0.4.tgz", - "integrity": "sha512-WQjmacEOoEYQb6CkAXYspmtruCPXFzLjEI92zV27yqveGBRkh7Quo5oYMvAgaEeqpXsGEr/wzXcdSTaH982Wtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-datepicker": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-6.2.0.tgz", - "integrity": "sha512-+JtO4Fm97WLkJTH8j8/v3Ldh7JCNRwjMYjRaKh4KHH0M3jJoXtwiD3JBCsdlg3tsFIw9eQSqyAPeVDN2H2oM9Q==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.2", - "@types/react": "*", - "date-fns": "^3.3.1" - } - }, - "node_modules/@types/react-datepicker/node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, "node_modules/@types/react-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", @@ -9881,6 +9850,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, "license": "MIT" }, "node_modules/anymatch": { @@ -9900,6 +9870,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -10134,43 +10105,6 @@ "node": ">=8.0.0" } }, - "node_modules/autoprefixer": { - "version": "10.4.27", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", - "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001774", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -10287,6 +10221,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10336,6 +10271,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -10497,6 +10433,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -10589,6 +10526,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -10613,6 +10551,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -10901,6 +10840,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -10980,7 +10920,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -11174,6 +11113,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/diff": { @@ -11195,6 +11135,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, "license": "MIT" }, "node_modules/dom-serializer": { @@ -11659,6 +11600,7 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -12756,6 +12698,7 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -12778,6 +12721,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -12887,20 +12831,6 @@ "node": ">= 6" } }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, "node_modules/framer-motion": { "version": "12.35.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.35.0.tgz", @@ -12932,6 +12862,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -13104,7 +13035,7 @@ "version": "4.13.6", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -13117,6 +13048,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -13587,6 +13519,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -13658,6 +13591,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -13714,6 +13648,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13771,6 +13706,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -13809,6 +13745,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -14163,6 +14100,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -14494,6 +14432,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -14506,6 +14445,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/linkify-it": { @@ -14912,6 +14852,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -14928,6 +14869,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -15064,6 +15006,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -15566,12 +15509,6 @@ "ufo": "^1.6.1" } }, - "node_modules/ogl": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz", - "integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==", - "license": "Unlicense" - }, "node_modules/oidc-token-hash": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", @@ -15835,6 +15772,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/pdfjs-dist": { @@ -15964,6 +15902,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -16246,6 +16185,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -16263,6 +16203,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -16288,6 +16229,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, "funding": [ { "type": "opencollective", @@ -16330,6 +16272,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -16355,19 +16298,6 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, "license": "MIT", "dependencies": { @@ -16382,6 +16312,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, "node_modules/preact": { @@ -16720,6 +16651,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -16968,6 +16900,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -16977,6 +16910,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17001,6 +16935,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -17082,6 +17017,7 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -17112,7 +17048,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -17155,6 +17091,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -17235,6 +17172,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -18115,6 +18053,7 @@ "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -18137,6 +18076,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -18155,6 +18095,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -18183,6 +18124,7 @@ "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -18216,19 +18158,11 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", - "license": "MIT", - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" - } - }, "node_modules/tailwindcss/node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -18245,6 +18179,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -18257,6 +18192,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -18266,6 +18202,7 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -18284,6 +18221,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -18293,6 +18231,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -18327,6 +18266,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -18343,6 +18283,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -18360,6 +18301,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -18396,6 +18338,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -18427,6 +18370,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/tsconfig-paths": { @@ -18465,6 +18409,7 @@ "version": "4.20.6", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -19484,7 +19429,7 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "devOptional": true, + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 79171f9a7..b8c898b60 100644 --- a/package.json +++ b/package.json @@ -87,14 +87,13 @@ "@tiptap/react": "3.29.2", "@tiptap/starter-kit": "3.29.2", "@tiptap/suggestion": "3.29.2", - "@types/qrcode.react": "^1.0.5", - "@types/react-datepicker": "^6.2.0", "@types/sanitize-html": "^2.13.0", "@vercel/analytics": "^1.5.0", "cal-sans": "^1.0.1", "chart.js": "^4.4.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^3.6.0", "dayjs": "^1.11.13", "framer-motion": "^12.23.0", "gsap": "^3.13.0", @@ -107,7 +106,6 @@ "markdown-it": "^14.3.0", "next": "^16.3.0", "next-auth": "^4.24.11", - "ogl": "^1.0.11", "pdfjs-dist": "^4.10.38", "persona-react": "6.3.1", "qrcode.react": "^4.2.0", @@ -123,27 +121,22 @@ "react-intersection-observer": "^9.16.0", "sanitize-html": "^2.14.0", "tailwind-merge": "^2.5.5", - "tailwindcss-animate": "^1.0.7", "tippy.js": "^6.3.7", "uuid": "^14.0.0", "zod": "^3.24.2" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.6", "@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/pro-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", "@playwright/test": "^1.62.1", - "@tailwindcss/typography": "^0.5.16", "@types/js-cookie": "^3.0.6", "@types/jsonwebtoken": "^9.0.7", "@types/lodash-es": "^4.17.12", "@types/markdown-it": "^14.1.2", "@types/node": "^20", "@types/react": "^19.2.18", - "@types/react-avatar-editor": "^13.0.4", "@types/react-dom": "^19.2.4", - "autoprefixer": "^10.4.20", "eslint": "^9.39.5", "eslint-config-next": "^16.3.0", "eslint-config-prettier": "^10.1.8", diff --git a/services/amplitude.service.ts b/services/amplitude.service.ts deleted file mode 100644 index 202b7bad6..000000000 --- a/services/amplitude.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as amplitude from '@amplitude/analytics-browser'; - -class AmplitudeService { - private static isInitialized = false; - - static init() { - if (this.isInitialized) { - return; - } - - const apiKey = process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY; - if (!apiKey) { - console.error('Amplitude API key not found.'); - return; - } - - amplitude.init(apiKey, { - defaultTracking: false, - serverUrl: process.env.NEXT_PUBLIC_AMPLITUDE_SERVER_URL, - }); - this.isInitialized = true; - } - - static logEvent(eventName: string, eventProperties?: Record) { - if (!this.isInitialized) { - console.error('Amplitude not initialized before logging event.'); - return; - } - amplitude.logEvent(eventName, eventProperties); - } -} - -export default AmplitudeService; diff --git a/services/journal.service.ts b/services/journal.service.ts deleted file mode 100644 index 17c1c494e..000000000 --- a/services/journal.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ApiClient } from './client'; -import { Journal } from '@/types/journal'; - -interface JournalResponse { - id: number; - name: string; - slug: string; - hub_image?: string; - description?: string; -} - -interface JournalsApiResponse { - results: JournalResponse[]; -} - -export class JournalService { - private static readonly BASE_PATH = '/api/search/journal'; - - static async getJournals(): Promise { - const response = await ApiClient.get(`${this.BASE_PATH}/`); - return response.results.map((journal) => ({ - id: journal.id, - name: journal.name, - slug: journal.slug, - imageUrl: journal.hub_image, - description: journal.description, - })); - } -} diff --git a/store/hubStore.ts b/store/hubStore.ts deleted file mode 100644 index 63f5c6121..000000000 --- a/store/hubStore.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Hub } from '@/types/hub'; - -export const hubs: Record = { - researchMethods: { - name: 'Research Methods', - slug: 'research-methods', - id: 22, - }, - neuroscience: { - name: 'Neuroscience', - slug: 'neuroscience', - id: 1, - }, - molecularBiology: { - name: 'Molecular Biology', - slug: 'molecular-biology', - id: 2, - }, - environmentalScience: { - name: 'Environmental Science', - slug: 'environmental-science', - id: 3, - }, - medicalDevices: { - name: 'Medical Devices', - slug: 'medical-devices', - id: 4, - }, - dataScience: { - name: 'Data Science', - slug: 'data-science', - id: 5, - }, - quantumComputing: { - name: 'Quantum Computing', - slug: 'quantum-computing', - id: 6, - }, - genetics: { - name: 'Genetics', - slug: 'genetics', - id: 7, - }, - artificialIntelligence: { - name: 'Artificial Intelligence', - slug: 'artificial-intelligence', - id: 8, - }, - climateScience: { - name: 'Climate Science', - slug: 'climate-science', - id: 9, - }, - biology: { - name: 'Biology', - slug: 'biology', - id: 10, - }, -}; diff --git a/tsconfig.json b/tsconfig.json index a9ea71793..63cd6027b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,5 +31,5 @@ ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], - "exclude": ["node_modules", "external_references", "app/notebook/[room]/page.tsx"] + "exclude": ["node_modules", "external_references"] } diff --git a/types/audit.ts b/types/audit.ts deleted file mode 100644 index c37677773..000000000 --- a/types/audit.ts +++ /dev/null @@ -1,483 +0,0 @@ -import { ID } from '@/types/root'; -import { AuthorProfile } from '@/types/authorProfile'; -import { User } from '@/types/user'; -import { FeedEntry, transformCommentToFeedItem } from '@/types/feed'; -import { ContentType } from '@/types/work'; -import { Comment } from '@/types/comment'; -import { - handleCommentContentJson, - getContentFromThreadDocument, - getFallbackContent, -} from '@/components/Moderators/utils/auditUtils'; - -/** - * Audit Types and Transformers - * - * This module provides: - * - Type definitions for audit/moderation system - * - Transform functions to convert audit data to feed components - * - Helper functions for content preview and display - * - * Used primarily by moderation dashboard and audit components - */ - -/** - * Represents flagged content in the audit system - */ -export interface FlaggedContent { - id: ID; - createdDate: string; - updatedDate?: string; - reason?: string; - reasonChoice?: string; - flaggedBy: User; - contentType: { - id: ID; - name: string; - }; - hubs?: Array<{ - id: number; - name: string; - slug: string; - }>; - /** Missing when the underlying content was deleted; API sends JSON `null`. */ - item?: null | { - id: ID; - createdBy?: AuthorProfile; - commentContentJson?: string | object; - content?: string; - text?: string; - title?: string; - description?: string; - renderableText?: string; - abstract?: string; - thread?: { - id: ID; - contentObject?: { - unifiedDocument?: { - documents?: Array<{ - id: ID; - title?: string; - renderableText?: string; - abstract?: string; - }>; - }; - }; - }; - }; - verdict?: { - id: ID; - verdictChoice: string; - createdBy: AuthorProfile; - }; -} - -// Transform functions following the established pattern - -/** - * Transform a FlaggedContent item to a FeedEntry for consistent rendering with existing feed components - */ -export const transformFlaggedContentToFeedEntry = ( - flaggedContent: FlaggedContent -): FeedEntry | undefined => { - const { item, contentType } = flaggedContent; - - if (!item) { - return undefined; - } - - const contentTypeName = contentType.name.toLowerCase(); - - // Handle different content types similar to the main feed transformer - switch (contentTypeName) { - case 'rhcommentmodel': - case 'comment': - return transformCommentForAudit(item, flaggedContent); - - case 'paper': - return transformPaperForAudit(item, flaggedContent); - - case 'bounty': - return transformBountyForAudit(item, flaggedContent); - - case 'researchhubpost': - case 'post': - return transformPostForAudit(item, flaggedContent); - - default: - // For unknown types, create a generic entry - return transformGenericForAudit(item, flaggedContent); - } -}; - -/** - * Transform a comment item for audit display - */ -const transformCommentForAudit = (commentItem: any, flaggedContent: FlaggedContent): FeedEntry => { - // Create a simplified Comment object from the audit item - const comment: Partial = { - id: commentItem.id, - content: commentItem.content ?? commentItem.commentContentJson, - contentFormat: commentItem.contentFormat ?? 'QUILL_EDITOR', - commentType: commentItem.commentType ?? 'GENERIC_COMMENT', - createdDate: commentItem.createdDate ?? flaggedContent.createdDate, - updatedDate: commentItem.updatedDate, - score: commentItem.score ?? 0, - reviewScore: commentItem.reviewScore ?? 0, - isRemoved: commentItem.isRemoved ?? false, - childrenCount: commentItem.childrenCount ?? 0, - // Simplified user creation for audit context - createdBy: { - id: commentItem.createdBy?.id ?? flaggedContent.flaggedBy.id, - firstName: commentItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName, - lastName: commentItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName, - fullName: `${commentItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName} ${commentItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName}`, - email: commentItem.createdBy?.email ?? '', - isVerified: commentItem.createdBy?.isVerified ?? false, - balance: 0, - lockedBalance: 0, - hasCompletedOnboarding: false, - moderator: false, - isModerator: false, - authorProfile: { - id: commentItem.createdBy?.id ?? flaggedContent.flaggedBy.id, - firstName: commentItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName, - lastName: commentItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName, - fullName: `${commentItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName} ${commentItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName}`, - profileImage: - commentItem.createdBy?.profileImage ?? - flaggedContent.flaggedBy.authorProfile?.profileImage ?? - '', - headline: '', - profileUrl: `/author/${commentItem.createdBy?.id ?? flaggedContent.flaggedBy.id}`, - isClaimed: false, - isVerified: false, - }, - }, - thread: commentItem.thread?.id - ? { - id: commentItem.thread.id, - threadType: - commentItem.thread.contentObject?.unifiedDocument?.documents?.[0].documentType ?? - 'PAPER', - objectId: - commentItem.thread.contentObject?.unifiedDocument?.documents?.[0].objectId ?? - commentItem.thread.id, - privacyType: 'PUBLIC', - raw: commentItem, - } - : { - id: 0, - threadType: 'PAPER', - objectId: 0, - privacyType: 'PUBLIC', - raw: commentItem, - }, - bounties: [], - tips: [], - replies: [], - raw: commentItem, - }; - - // Determine content type for related work - const relatedContentType: ContentType = - commentItem.thread?.contentObject?.unifiedDocument?.documents?.[0].documentType === 'POST' - ? 'post' - : 'paper'; - - return transformCommentToFeedItem(comment as Comment, relatedContentType); -}; - -/** - * Transform a paper item for audit display - */ -const transformPaperForAudit = (paperItem: any, flaggedContent: FlaggedContent): FeedEntry => { - // Create a minimal FeedEntry for paper - return { - id: `audit-paper-${paperItem.id}`, - recommendationId: null, - timestamp: paperItem.createdDate ?? flaggedContent.createdDate, - action: 'publish', - contentType: 'PAPER', - content: { - id: paperItem.id, - contentType: 'PAPER', - createdDate: paperItem.createdDate ?? flaggedContent.createdDate, - textPreview: paperItem.abstract ?? paperItem.title ?? '', - slug: paperItem.slug ?? `paper-${paperItem.id}`, - title: paperItem.title ?? 'Untitled Paper', - authors: paperItem.authors ?? [], - topics: paperItem.hubs ?? [], - createdBy: { - id: paperItem.createdBy?.id ?? flaggedContent.flaggedBy.id, - firstName: paperItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName, - lastName: paperItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName, - fullName: `${paperItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName} ${paperItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName}`, - profileImage: - paperItem.createdBy?.profileImage ?? - flaggedContent.flaggedBy.authorProfile?.profileImage ?? - '', - headline: '', - profileUrl: `/author/${paperItem.createdBy?.id ?? flaggedContent.flaggedBy.id}`, - isClaimed: false, - isVerified: false, - }, - journal: paperItem.journal ?? { id: 0, name: '', slug: '', image: null, description: '' }, - }, - metrics: { - votes: paperItem.score ?? 0, - comments: paperItem.discussionCount ?? 0, - saves: 0, - reviewScore: 0, - }, - }; -}; - -/** - * Transform a bounty item for audit display - */ -const transformBountyForAudit = (bountyItem: any, flaggedContent: FlaggedContent): FeedEntry => { - // Create a minimal FeedEntry for bounty - return { - id: `audit-bounty-${bountyItem.id}`, - recommendationId: null, - timestamp: bountyItem.createdDate ?? flaggedContent.createdDate, - action: 'open', - contentType: 'BOUNTY', - content: { - id: bountyItem.id, - contentType: 'BOUNTY', - createdDate: bountyItem.createdDate ?? flaggedContent.createdDate, - bounty: bountyItem, - createdBy: { - id: bountyItem.createdBy?.id ?? flaggedContent.flaggedBy.id, - firstName: bountyItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName, - lastName: bountyItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName, - fullName: `${bountyItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName} ${bountyItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName}`, - profileImage: - bountyItem.createdBy?.profileImage ?? - flaggedContent.flaggedBy.authorProfile?.profileImage ?? - '', - headline: '', - profileUrl: `/author/${bountyItem.createdBy?.id ?? flaggedContent.flaggedBy.id}`, - isClaimed: false, - isVerified: false, - }, - comment: { - id: bountyItem.id, - content: bountyItem.content ?? bountyItem.description, - contentFormat: 'QUILL_EDITOR', - commentType: 'BOUNTY', - }, - }, - metrics: { - votes: bountyItem.score ?? 0, - comments: 0, - saves: 0, - reviewScore: 0, - }, - }; -}; - -/** - * Transform a post item for audit display - */ -const transformPostForAudit = (postItem: any, flaggedContent: FlaggedContent): FeedEntry => { - return { - id: `audit-post-${postItem.id}`, - recommendationId: null, - timestamp: postItem.createdDate ?? flaggedContent.createdDate, - action: 'post', - contentType: 'POST', - content: { - id: postItem.id, - contentType: 'POST', - createdDate: postItem.createdDate ?? flaggedContent.createdDate, - textPreview: postItem.renderableText ?? postItem.title ?? '', - slug: postItem.slug ?? `post-${postItem.id}`, - title: postItem.title ?? 'Untitled Post', - authors: postItem.authors ?? [], - topics: postItem.hubs ?? [], - createdBy: { - id: postItem.createdBy?.id ?? flaggedContent.flaggedBy.id, - firstName: postItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName, - lastName: postItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName, - fullName: `${postItem.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName} ${postItem.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName}`, - profileImage: - postItem.createdBy?.profileImage ?? - flaggedContent.flaggedBy.authorProfile?.profileImage ?? - '', - headline: '', - profileUrl: `/author/${postItem.createdBy?.id ?? flaggedContent.flaggedBy.id}`, - isClaimed: false, - isVerified: false, - }, - }, - metrics: { - votes: postItem.score ?? 0, - comments: postItem.discussionCount ?? 0, - saves: 0, - reviewScore: 0, - }, - }; -}; - -/** - * Transform a generic/unknown item for audit display - */ -const transformGenericForAudit = (item: any, flaggedContent: FlaggedContent): FeedEntry => { - return { - id: `audit-generic-${item.id ?? flaggedContent.id}`, - recommendationId: null, - timestamp: item.createdDate ?? flaggedContent.createdDate, - action: 'contribute', - contentType: 'POST', // Default to POST for unknown types - content: { - id: item.id ?? flaggedContent.id, - contentType: 'POST', - createdDate: item.createdDate ?? flaggedContent.createdDate, - textPreview: item.content ?? item.description ?? item.title ?? 'Content not available', - slug: item.slug ?? `content-${item.id ?? flaggedContent.id}`, - title: item.title ?? `${flaggedContent.contentType.name} Content`, - authors: [], - topics: flaggedContent.hubs ?? [], - createdBy: { - id: item.createdBy?.id ?? flaggedContent.flaggedBy.id, - firstName: item.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName, - lastName: item.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName, - fullName: `${item.createdBy?.firstName ?? flaggedContent.flaggedBy.firstName} ${item.createdBy?.lastName ?? flaggedContent.flaggedBy.lastName}`, - profileImage: - item.createdBy?.profileImage ?? - flaggedContent.flaggedBy.authorProfile?.profileImage ?? - '', - headline: '', - profileUrl: `/author/${item.createdBy?.id ?? flaggedContent.flaggedBy.id}`, - isClaimed: false, - isVerified: false, - }, - }, - metrics: { - votes: item.score ?? 0, - comments: 0, - saves: 0, - reviewScore: 0, - }, - }; -}; - -/** - * Get display title for a flagged content entry - */ -export const getFlaggedContentTitle = (entry: FlaggedContent): string => { - const contentType = entry.contentType.name.toLowerCase(); - const flaggedByName = `${entry.flaggedBy.firstName} ${entry.flaggedBy.lastName}`; - - if (contentType.includes('comment')) { - return `Comment flagged by ${flaggedByName}`; - } else if (contentType.includes('review')) { - return `Peer review flagged by ${flaggedByName}`; - } else if (contentType.includes('bounty')) { - return `Bounty flagged by ${flaggedByName}`; - } else if (contentType.includes('paper')) { - return `Paper flagged by ${flaggedByName}`; - } else if (contentType.includes('post')) { - return `Post flagged by ${flaggedByName}`; - } else { - return `${entry.contentType.name} flagged by ${flaggedByName}`; - } -}; - -/** - * Get content preview from flagged content entry - */ -export const getFlaggedContentPreview = (entry: FlaggedContent): string => { - if (!entry.item) { - return 'Content not available'; - } - - const contentItem = entry.item; - - // For comments with TipTap or Quill JSON content - if (contentItem.commentContentJson) { - return handleCommentContentJson(contentItem.commentContentJson); - } - - // For posts and papers, get content from thread content object - const threadContent = getContentFromThreadDocument(contentItem); - if (threadContent) { - return threadContent; - } - - // Fallback to other possible content fields - return getFallbackContent(contentItem); -}; - -/** - * Get the parent document title from flagged content entry - */ -export const getFlaggedContentParentDocumentTitle = (entry: FlaggedContent): string => { - const documents = entry.item?.thread?.contentObject?.unifiedDocument?.documents; - if (documents && documents.length > 0) { - return documents[0].title ?? 'Untitled Document'; - } - return 'No parent document'; -}; - -/** - * Get offending user information from flagged content entry - */ -export const getFlaggedContentOffendingUser = (entry: FlaggedContent) => { - if (entry.item?.createdBy) { - const createdBy = entry.item.createdBy; - return { - name: `${createdBy.firstName ?? ''} ${createdBy.lastName ?? ''}`.trim() || 'Unknown User', - avatar: createdBy.profileImage ?? null, - authorId: createdBy.id ?? null, - isRemoved: false, - }; - } - - // If createdBy is null, this likely means the content was removed - return { - name: 'Removed User', - avatar: null, - authorId: null, - isRemoved: true, - }; -}; - -/** - * Get status color class based on verdict - */ -export const getFlaggedContentStatusColor = (verdict?: string): string => { - if (!verdict) return 'bg-orange-100 text-orange-800'; - - switch (verdict.toLowerCase()) { - case 'open': - return 'bg-orange-100 text-orange-800'; - case 'removed': - return 'bg-red-100 text-red-800'; - case 'approved': - return 'bg-green-100 text-green-800'; - default: - return 'bg-gray-100 text-gray-800'; - } -}; - -/** - * Get display status based on verdict - */ -export const getFlaggedContentDisplayStatus = (verdict?: string): string => { - if (!verdict) return 'Pending'; - - switch (verdict.toLowerCase()) { - case 'open': - return 'Pending'; - case 'removed': - return 'Removed'; - case 'approved': - return 'Dismissed'; - default: - return 'Unknown'; - } -}; diff --git a/utils/avatarUtil.ts b/utils/avatarUtil.ts deleted file mode 100644 index be7395995..000000000 --- a/utils/avatarUtil.ts +++ /dev/null @@ -1,29 +0,0 @@ -interface AvatarItem { - src: string; - alt: string; - tooltip?: string; - authorId?: number; -} - -/** - * Deduplicates an array of avatar items based on authorId. - * Keeps the first occurrence of each authorId. - */ -export const dedupeAvatars = (avatars: AvatarItem[]): AvatarItem[] => { - if (!avatars) return []; - - const seenAuthorIds = new Set(); - return avatars.filter((avatar) => { - // If authorId is undefined or null, keep the avatar (cannot dedupe) - if (avatar.authorId === undefined || avatar.authorId === null) { - return true; - } - // If authorId has been seen, filter it out - if (seenAuthorIds.has(avatar.authorId)) { - return false; - } - // Otherwise, mark authorId as seen and keep the avatar - seenAuthorIds.add(avatar.authorId); - return true; - }); -};