104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
interface MeiliHit {
|
|
id: string;
|
|
content: string;
|
|
senderName: string;
|
|
sourceGroupName: string;
|
|
tags: string[];
|
|
approvedAt: number;
|
|
}
|
|
|
|
interface SearchResponse {
|
|
hits: MeiliHit[];
|
|
total: number;
|
|
page: number;
|
|
limit: number;
|
|
query: string;
|
|
}
|
|
|
|
export function SearchResults({
|
|
hits,
|
|
total,
|
|
q,
|
|
page,
|
|
}: {
|
|
hits: MeiliHit[];
|
|
total: number;
|
|
q: string;
|
|
page: number;
|
|
}) {
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<form method="GET" className="flex gap-2">
|
|
<input
|
|
name="q"
|
|
defaultValue={q}
|
|
placeholder="Search approved messages…"
|
|
className="flex-1 rounded-lg border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
|
>
|
|
Search
|
|
</button>
|
|
</form>
|
|
|
|
{hits.length === 0 ? (
|
|
<p className="text-gray-500 text-sm">No results{q ? ` for "${q}"` : ''}.</p>
|
|
) : (
|
|
<>
|
|
<p className="text-sm text-gray-500">
|
|
{total} result{total !== 1 ? 's' : ''}
|
|
</p>
|
|
<ul className="flex flex-col gap-3">
|
|
{hits.map((hit) => (
|
|
<li key={hit.id} className="rounded-xl border border-gray-200 bg-white p-4">
|
|
<p className="text-sm">{hit.content}</p>
|
|
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-gray-400">
|
|
<span>{hit.senderName}</span>
|
|
<span>·</span>
|
|
<span>{hit.sourceGroupName}</span>
|
|
<span>·</span>
|
|
<span>{new Date(hit.approvedAt).toLocaleDateString()}</span>
|
|
{hit.tags.map((tag) => (
|
|
<span key={tag} className="rounded-full bg-blue-50 px-2 py-0.5 text-blue-600">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default async function SearchPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ q?: string; page?: string }>;
|
|
}) {
|
|
const { q = '', page = '1' } = await searchParams;
|
|
const apiUrl = process.env.API_URL ?? 'http://localhost:3001';
|
|
const url = new URL(`${apiUrl}/search`);
|
|
url.searchParams.set('q', q);
|
|
url.searchParams.set('page', page);
|
|
|
|
let data: SearchResponse = { hits: [], total: 0, page: 1, limit: 20, query: q };
|
|
try {
|
|
const res = await fetch(url, { cache: 'no-store' });
|
|
if (res.ok) data = await res.json();
|
|
} catch {
|
|
// API unavailable — render empty results
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-2xl">
|
|
<h1 className="text-xl font-semibold mb-4">Search</h1>
|
|
<SearchResults hits={data.hits} total={data.total} q={q} page={Number(page)} />
|
|
</div>
|
|
);
|
|
}
|