Files
tower/apps/web/app/my/directory/DirectorySearch.tsx
T
maaz519 6d89d8a609 feat: member portal Sprint 8 — Member Directory
- 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>
2026-06-17 16:55:30 +05:30

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>
);
}