diff --git a/app/layouts/LeftSidebar.tsx b/app/layouts/LeftSidebar.tsx index 707f92df2..cf4433875 100644 --- a/app/layouts/LeftSidebar.tsx +++ b/app/layouts/LeftSidebar.tsx @@ -2,7 +2,6 @@ import { AlertCircle } from 'lucide-react'; import { usePathname } from 'next/navigation'; -import { FooterLinks } from '../../components/FooterLinks'; import { Navigation } from './Navigation'; import toast from 'react-hot-toast'; import { PublishMenu } from './PublishMenu'; @@ -11,6 +10,7 @@ import Link from 'next/link'; import { Icon } from '@/components/ui/icons'; import { FundingPowerRailButton } from '@/components/Funding/FundingPowerRailButton'; import { SidebarDocuments } from './components/SidebarDocuments'; +import { SidebarHelpMenu } from './components/SidebarHelpMenu'; interface LeftSidebarProps { forceMinimize?: boolean; @@ -86,7 +86,7 @@ export const LeftSidebar: React.FC = ({ forceMinimize = false,
- +
); diff --git a/app/layouts/components/RightSidebarContainer.tsx b/app/layouts/components/RightSidebarContainer.tsx index 21212e604..31f1a82d6 100644 --- a/app/layouts/components/RightSidebarContainer.tsx +++ b/app/layouts/components/RightSidebarContainer.tsx @@ -4,12 +4,12 @@ import { ReactNode, Suspense } from 'react'; import { cn } from '@/lib/utils'; import { useWorkTab } from '@/components/work/WorkHeader/WorkTabContext'; import { SwipeableDrawer } from '@/components/ui/SwipeableDrawer'; -import { FundSidebar } from '@/components/Funding/FundSidebar'; +import { RecentlyVisited } from '@/components/RecentlyVisited/RecentlyVisited'; import { FundingPowerCard } from '@/components/Funding/FundingPowerCard'; function RightSidebarContent({ rightSidebar }: { rightSidebar: boolean | ReactNode }) { if (typeof rightSidebar === 'boolean') { - return ; + return ; } return <>{rightSidebar}; diff --git a/app/layouts/components/SidebarHelpMenu.tsx b/app/layouts/components/SidebarHelpMenu.tsx new file mode 100644 index 000000000..9f6d49248 --- /dev/null +++ b/app/layouts/components/SidebarHelpMenu.tsx @@ -0,0 +1,171 @@ +'use client'; + +import Link from 'next/link'; +import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { + ArrowUpRight, + BookOpen, + ChevronUp, + CircleHelp, + Info, + LifeBuoy, + Megaphone, + type LucideIcon, +} from 'lucide-react'; +import { faXTwitter, faDiscord, faGithub, faLinkedin } from '@fortawesome/free-brands-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useChangelogSeen } from '@/components/changelog/useChangelogSeen'; +import { BaseMenu } from '@/components/ui/form/BaseMenu'; +import { RadiatingDot } from '@/components/ui/RadiatingDot'; +import { useCurrencyPreference } from '@/contexts/CurrencyPreferenceContext'; + +const RESOURCE_LINKS: { label: string; href: string; icon: LucideIcon }[] = [ + { label: 'Docs', href: 'https://docs.researchhub.com/', icon: BookOpen }, + { + label: 'Support', + href: 'https://airtable.com/appuhMJaf1kb3ic8e/pagYeh6cB9sgiTIgx/form', + icon: LifeBuoy, + }, + { label: 'About', href: 'https://www.researchhub.com/about', icon: Info }, +]; + +const SOCIAL_LINKS = [ + { label: 'X', href: 'https://x.com/researchhub', icon: faXTwitter }, + { label: 'Discord', href: 'https://discord.com/invite/ZcCYgcnUp5', icon: faDiscord }, + { label: 'GitHub', href: 'https://github.com/ResearchHub', icon: faGithub }, + { + label: 'LinkedIn', + href: 'https://www.linkedin.com/company/researchhubtechnologies', + icon: faLinkedin, + }, +]; + +const LEGAL_LINKS = [ + { label: 'Terms', href: 'https://www.researchhub.com/tos' }, + { label: 'Privacy', href: 'https://www.researchhub.com/privacy' }, +]; + +const CURRENCIES = ['USD', 'RSC'] as const; + +const MENU_ROW = + 'flex cursor-pointer items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm text-gray-700 outline-none transition-colors data-[highlighted]:bg-gray-50'; + +const SEPARATOR = 'mx-1 my-1.5 h-px bg-gray-100'; + +/** + * The sidebar's footer as a single row. What used to be spread across it (the + * changelog, docs, support, currency, socials and legal links) opens from it + * in one menu, so the column keeps its height for navigation and documents. + */ +export function SidebarHelpMenu() { + const { hasSeen: hasSeenChangelog, markSeen: markChangelogSeen } = useChangelogSeen(); + const { showUSD, toggleCurrency } = useCurrencyPreference(); + const currency = showUSD ? 'USD' : 'RSC'; + + return ( +
+ + +
+ ); +} diff --git a/components/FooterLinks.tsx b/components/FooterLinks.tsx deleted file mode 100644 index b626fe877..000000000 --- a/components/FooterLinks.tsx +++ /dev/null @@ -1,83 +0,0 @@ -'use client'; - -import { faXTwitter, faDiscord, faGithub, faLinkedin } from '@fortawesome/free-brands-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ChangelogLink } from '@/components/changelog/ChangelogLink'; -import { useCurrencyPreference } from '@/contexts/CurrencyPreferenceContext'; - -export const FooterLinks: React.FC = () => { - const { showUSD, toggleCurrency } = useCurrencyPreference(); - - return ( -
-
-
- - - - - - - - - - - - - {/* Currency Dropdown */} - -
-
-
- - About - - - Docs - - - - Terms - - - Privacy - - - Support - -
-
- ); -}; diff --git a/components/Funding/FundSidebar.tsx b/components/RecentlyVisited/RecentlyVisited.tsx similarity index 76% rename from components/Funding/FundSidebar.tsx rename to components/RecentlyVisited/RecentlyVisited.tsx index 29f672dcc..0d03f6783 100644 --- a/components/Funding/FundSidebar.tsx +++ b/components/RecentlyVisited/RecentlyVisited.tsx @@ -6,7 +6,8 @@ import { useRecentlyVisited, } from './RecentlyVisitedCard'; -export function FundSidebar() { +/** The default right sidebar's Recently visited section, once local history is readable. */ +export function RecentlyVisited() { const { pages, clear, isHydrated } = useRecentlyVisited(); if (!isHydrated) { diff --git a/components/Funding/RecentlyVisitedCard.tsx b/components/RecentlyVisited/RecentlyVisitedCard.tsx similarity index 64% rename from components/Funding/RecentlyVisitedCard.tsx rename to components/RecentlyVisited/RecentlyVisitedCard.tsx index 79445744a..c267cbc30 100644 --- a/components/Funding/RecentlyVisitedCard.tsx +++ b/components/RecentlyVisited/RecentlyVisitedCard.tsx @@ -2,21 +2,19 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; -import { getSearchHistory, clearSearchHistory, MAX_HISTORY_ITEMS } from '@/utils/searchHistory'; +import { getSearchHistory, clearSearchHistory } from '@/utils/searchHistory'; import { buildWorkUrl } from '@/utils/url'; +import { formatTimeAgo } from '@/utils/date'; import { SearchSuggestion } from '@/types/search'; import { ContentType } from '@/types/work'; import { cn } from '@/utils/styles'; -import { FieldLabel } from '@/components/ui/FieldLabel'; +import { RecentlyVisitedThumbnail } from './RecentlyVisitedThumbnail'; -const WORK_TYPE_LABELS: Record = { - paper: 'Paper', - post: 'Post', - preregistration: 'Proposal', - question: 'Question', - discussion: 'Discussion', - funding_request: 'Request for Proposal', -}; +/** + * Storage keeps more visits than the rail lists (the search modal reads them + * too); six rows fit under the funding power card on a 900px-tall window. + */ +const MAX_VISIBLE = 6; const SKELETON_ROWS = [ { title: ['w-[92%]', 'w-[68%]'], meta: 'w-16' }, @@ -24,12 +22,17 @@ const SKELETON_ROWS = [ { title: ['w-[90%]', 'w-[72%]'], meta: 'w-20' }, { title: ['w-[78%]', 'w-[48%]'], meta: 'w-14' }, { title: ['w-[88%]', 'w-[60%]'], meta: 'w-16' }, + { title: ['w-[82%]', 'w-[64%]'], meta: 'w-12' }, ] as const; interface RecentPage { href: string; title: string; - typeLabel?: string; + isPaper: boolean; + imageUrl?: string; + authorImage?: string; + /** ISO time of the visit; entries saved before it was recorded have none. */ + lastVisited?: string; } export interface RecentlyVisited { @@ -57,7 +60,10 @@ function toRecentPage(visit: VisitRecord): RecentPage | null { return { href, title, - typeLabel: WORK_TYPE_LABELS[contentType], + isPaper: contentType === 'paper', + imageUrl: visit.imageUrl, + authorImage: visit.authorImage, + lastVisited: visit.lastVisited, }; } @@ -65,7 +71,7 @@ function toRecentPage(visit: VisitRecord): RecentPage | null { return { href: visit.url || `/post/${visit.id}`, title, - typeLabel: WORK_TYPE_LABELS.post, + isPaper: false, }; } @@ -82,7 +88,7 @@ function visitsToPages(visits: VisitRecord[]): RecentPage[] { if (!page || seen.has(page.href)) continue; seen.add(page.href); collected.push(page); - if (collected.length === MAX_HISTORY_ITEMS) break; + if (collected.length === MAX_VISIBLE) break; } return collected; @@ -128,8 +134,8 @@ interface RecentlyVisitedCardProps extends Omit { } /** - * Lightweight browsing history for the Activity sidebar: a plain text list of - * documents from local visit history, no thumbnails or metrics. + * Browsing history for the Activity sidebar: documents from local visit + * history, each with its picture and when it was last opened. */ export function RecentlyVisitedCard({ pages, clear, className }: RecentlyVisitedCardProps) { if (pages.length === 0) return null; @@ -137,29 +143,38 @@ export function RecentlyVisitedCard({ pages, clear, className }: RecentlyVisited return (