diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 42d93ac4..8f3d5b4f 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -119,7 +119,7 @@ function SiteFooter() { * form/prose-style pages, wide (full width) for landing / dashboard / detail * pages that render their own internal layout. */ -function FundraiserLayout({ narrow }: { narrow: boolean }) { +function FundraiserLayout({ narrow, hideFooter }: { narrow: boolean; hideFooter?: boolean }) { return (
@@ -130,7 +130,7 @@ function FundraiserLayout({ narrow }: { narrow: boolean }) {
- + {!hideFooter && } ); } @@ -200,6 +200,10 @@ export function AppRouter() { } /> + }> + } /> + + {/* Wide layout — no max-width on the center column. Used by landing / list / detail pages that render their own internal width constraints. */} @@ -218,7 +222,6 @@ export function AppRouter() { } /> } /> } /> - } /> } /> {/* About page + Donor / Recipient guides. Full-bleed landing-style layouts that render their own internal max-widths. */} diff --git a/src/pages/MessagesPage.tsx b/src/pages/MessagesPage.tsx index d704d292..19aa1fb3 100644 --- a/src/pages/MessagesPage.tsx +++ b/src/pages/MessagesPage.tsx @@ -3,11 +3,12 @@ import { useEffect, useMemo, useRef, useState, type FormEvent, type KeyboardEven import { Navigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useInView } from 'react-intersection-observer'; -import { ArrowLeft, Inbox, Loader2, Lock, MessageSquare, Send } from 'lucide-react'; +import { ArrowLeft, Loader2, Lock, MessageSquare, Search, Send } from 'lucide-react'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Skeleton } from '@/components/ui/skeleton'; import { Textarea } from '@/components/ui/textarea'; @@ -176,13 +177,10 @@ function MessageThread({ conversation, onBack }: { conversation: Conversation; o {name.charAt(0)} -
-

{name}

-

{t('messages.subtitle')}

-
+

{name}

- +
{isLoading ? (
@@ -232,6 +230,7 @@ export function MessagesPage() { pageCount, } = useDirectMessages(); const [selectedPeer, setSelectedPeer] = useState(null); + const [search, setSearch] = useState(''); const { ref: olderMessagesRef, inView: olderMessagesInView } = useInView({ threshold: 0, rootMargin: '300px' }); useSeoMeta({ @@ -244,6 +243,15 @@ export function MessagesPage() { [conversations, selectedPeer], ); + const filteredConversations = useMemo(() => { + const query = search.trim().toLocaleLowerCase(); + if (!query) return conversations; + return conversations?.filter((conversation) => { + const latest = conversation.latest.content?.toLocaleLowerCase() ?? ''; + return conversation.peer.toLocaleLowerCase().includes(query) || latest.includes(query); + }); + }, [conversations, search]); + useEffect(() => { if (pageCount === 1 && hasNextPage && !isFetchingNextPage) { void fetchNextPage(); @@ -263,33 +271,36 @@ export function MessagesPage() { const hasDmSupport = !!user.signer.nip04; return ( -
+
{!hasDmSupport ? ( -
+

{t('messages.unsupported')}

) : ( -
+
{/* Conversation list */}
-
-
-
-

{t('messages.conversations')}

-

NIP-04

-
- +
+
+ + setSearch(e.target.value)} + placeholder={t('nav.search')} + aria-label={t('nav.search')} + className="h-10 rounded-xl bg-background pl-9" + />
- +
{isLoading ? ( Array.from({ length: 5 }).map((_, i) => ( @@ -301,8 +312,8 @@ export function MessagesPage() {
)) - ) : conversations && conversations.length > 0 ? ( - conversations.map((conversation) => ( + ) : filteredConversations && filteredConversations.length > 0 ? ( + filteredConversations.map((conversation) => (
)} - {hasNextPage && conversations && conversations.length > 0 && ( + {hasNextPage && conversations && conversations.length > 0 && !search.trim() && (
{isFetchingNextPage && }
@@ -327,7 +338,7 @@ export function MessagesPage() {
{/* Thread */} -
+
{selected ? ( setSelectedPeer(null)} /> ) : (