Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions app/routes/search-results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

import { Badge, Button, Loader, Pagination, Tooltip } from "@cloudflare/kumo";
import { Badge, Button, Loader, Pagination, Tooltip, useKumoToastManager } from "@cloudflare/kumo";
import { ArrowLeftIcon, MagnifyingGlassIcon } from "@phosphor-icons/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useParams, useSearchParams } from "react-router";
Expand Down Expand Up @@ -35,6 +35,7 @@ export default function SearchResultsRoute() {
const navigate = useNavigate();
const { selectedEmailId, isComposing, selectEmail, closePanel } = useUIStore();
const updateEmail = useUpdateEmail();
const toastManager = useKumoToastManager();
const urlQuery = searchParams.get("q") || "";
const [page, setPage] = useState(1);
const searchKey = useMemo(
Expand All @@ -55,14 +56,21 @@ export default function SearchResultsRoute() {
closePanel();
}, [closePanel, searchChanged, searchKey]);

const { data: searchData, isLoading } = useSearchEmails(
const { data: searchData, error, errorUpdatedAt, isError, isFetching, isLoading, refetch } = useSearchEmails(
mailboxId,
urlQuery,
currentPage,
);
const results = searchData?.results ?? [];
const totalCount = searchData?.totalCount ?? 0;
const isPanelOpen = selectedEmailId !== null || isComposing;
const errorMessage = error instanceof Error ? error.message : "Try again in a moment.";

useEffect(() => {
if (isError) {
toastManager.add({ title: "Search failed", variant: "error" });
}
}, [errorUpdatedAt, isError, toastManager]);

const handleRowClick = (email: Email) => { selectEmail(email.id); if (!email.read && mailboxId) updateEmail.mutate({ mailboxId, id: email.id, data: { read: true } }); };
const folderDisplayName = (name: string | null | undefined): string => { if (!name) return ""; const map: Record<string, string> = { inbox: "Inbox", sent: "Sent", draft: "Drafts", archive: "Archive", trash: "Trash" }; return map[name.toLowerCase()] || name; };
Expand All @@ -75,10 +83,17 @@ export default function SearchResultsRoute() {
<>
<div className="flex items-center gap-2 px-4 py-3.5 border-b border-kumo-line shrink-0 md:px-5">
<Tooltip content="Back to inbox" side="bottom" asChild><Button variant="ghost" shape="square" size="sm" icon={<ArrowLeftIcon size={18} />} onClick={() => navigate(`/mailbox/${mailboxId}/emails/inbox`)} aria-label="Back to inbox" /></Tooltip>
<div className="min-w-0 flex-1"><h1 className="text-lg font-semibold text-kumo-default truncate">Search Results</h1>{!isLoading && <span className="text-sm text-kumo-subtle">{totalCount} result{totalCount !== 1 ? "s" : ""}{urlQuery ? ` for "${urlQuery}"` : ""}</span>}</div>
<div className="min-w-0 flex-1"><h1 className="text-lg font-semibold text-kumo-default truncate">Search Results</h1>{!isLoading && !isError && <span className="text-sm text-kumo-subtle">{totalCount} result{totalCount !== 1 ? "s" : ""}{urlQuery ? ` for "${urlQuery}"` : ""}</span>}</div>
</div>
<div className="flex-1 overflow-y-auto">
{isLoading ? <div className="flex justify-center py-16"><Loader size="lg" /></div> : results.length === 0 ? (
{isLoading ? <div className="flex justify-center py-16"><Loader size="lg" /></div> : isError ? (
<div className="flex flex-col items-center justify-center py-24 px-6 text-center">
<div className="mb-4"><MagnifyingGlassIcon size={48} weight="thin" className="text-kumo-subtle" /></div>
<h3 className="text-base font-semibold text-kumo-default mb-1.5">Search failed</h3>
<p className="text-sm text-kumo-subtle max-w-xs mb-5">{errorMessage}</p>
<Button size="sm" onClick={() => refetch()} disabled={isFetching}>{isFetching ? "Retrying..." : "Try again"}</Button>
</div>
) : results.length === 0 ? (
<div className="flex flex-col items-center justify-center py-24 px-6 text-center">
<div className="mb-4"><MagnifyingGlassIcon size={48} weight="thin" className="text-kumo-subtle" /></div>
<h3 className="text-base font-semibold text-kumo-default mb-1.5">No results found</h3>
Expand Down