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
-
-
+
-
+
{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)} />
) : (