6d89d8a609
- GET /my/directory in MyService/MyController with q + interest filters - Respects directoryVisible; excludes self; never exposes jid/phone - q searches displayName/hometown/currentLocation (case-insensitive); interest filter via array `has`; aggregates top-20 interest filter chips - BFF route forwards query string - /my/directory page: server-rendered cards (avatar initials, location, interest tags) - DirectorySearch client component: debounced search box + toggleable interest chips that drive URL params - Directory nav item Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
'use client';
|
|
|
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
import { useState, useEffect } from 'react';
|
|
|
|
interface Props {
|
|
interests: Array<{ name: string; count: number }>;
|
|
}
|
|
|
|
export function DirectorySearch({ interests }: Props) {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const activeInterest = searchParams.get('interest');
|
|
const [q, setQ] = useState(searchParams.get('q') ?? '');
|
|
|
|
// Debounced push of the search term into the URL.
|
|
useEffect(() => {
|
|
const handle = setTimeout(() => {
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
if (q.trim()) params.set('q', q.trim());
|
|
else params.delete('q');
|
|
router.replace(`/my/directory${params.toString() ? `?${params.toString()}` : ''}`);
|
|
}, 300);
|
|
return () => clearTimeout(handle);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [q]);
|
|
|
|
const toggleInterest = (name: string) => {
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
if (activeInterest === name) params.delete('interest');
|
|
else params.set('interest', name);
|
|
router.replace(`/my/directory${params.toString() ? `?${params.toString()}` : ''}`);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<input
|
|
type="text"
|
|
value={q}
|
|
onChange={(e) => setQ(e.target.value)}
|
|
placeholder="Search by name, hometown, or location…"
|
|
className="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
|
/>
|
|
{interests.length > 0 && (
|
|
<div className="flex flex-wrap gap-2">
|
|
{interests.map((i) => (
|
|
<button
|
|
key={i.name}
|
|
type="button"
|
|
onClick={() => toggleInterest(i.name)}
|
|
className={`text-xs rounded-full px-3 py-1 transition-colors ${
|
|
activeInterest === i.name
|
|
? 'bg-indigo-600 text-white'
|
|
: 'bg-indigo-50 text-indigo-700 hover:bg-indigo-100'
|
|
}`}
|
|
>
|
|
{i.name} <span className="opacity-60">{i.count}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|