metatron-open-seo/src/client/features/ai-search/components/PromptExplorerHistorySection.tsx
Ben Senescu c1a9c1f57d
Replace mutation-based search with URL-driven state management (#145)
* fix: preserve cmd+click on tracked domain rows

Tracked-domain rows used <div role="button" onClick={navigate(...)}>,
which prevented standard browser open-in-new-tab behavior (cmd+click,
middle-click, right-click → open). Use TanStack Router's <Link> as a
stretched overlay so the row is a real <a href> while the archive
button stays interactive.

* fix: use <Link> for tab toggles and history navigation

Replace onClick={() => navigate(...)} / setSearchParams patterns with
TanStack Router's <Link> across surfaces that change the URL on click.
<Link> renders a real <a href> and only intercepts plain left-clicks,
so cmd/ctrl+click, middle-click, and right-click → open-in-new-tab
all work natively.

- Audit: history "View" button + Pages/Performance tab toggles.
- Domain overview: Top Keywords / Top Pages tab toggles. The keyword-
  only sort fallback now happens in the Link's search updater.
- Backlinks: history items, Backlinks/Domains/Pages tab toggles, and
  "Recent searches" back-link. Removes now-unused
  navigateToBacklinksHistory / navigateToBacklinksTab helpers.

Keyword research and domain history items, and AI search histories,
are not converted: those pages don't trigger their data fetch from
URL params alone, so a plain link target wouldn't reproduce the
current click behavior without a deeper refactor.

* refactor: unify search-page state around URL-driven fetching

Drive Keyword Research, Brand Lookup, and Prompt Explorer from URL
search params so a search is reproducible from a link alone. With
that, all three history surfaces become <Link>s and cmd+click /
right-click → "open in new tab" work natively.

- Shared SearchHistorySection now takes a renderItemLink slot so
  callers wrap history items in a <Link> with the right destination.
- Prompt Explorer: added URL search params (q, models, web, cc, hb)
  via promptExplorerSearchSchema; switched the explore mutation to
  useQuery keyed on the URL params; addSearch now fires from a
  success effect; "Recent searches" back-button is a <Link>.
- Brand Lookup: history items + "Recent searches" back-button are
  <Link>s; local form state stays in sync with URL via an effect.
- Keyword Research: form submit still navigates+kicks off a search
  for the same-URL re-submit case, but the controller also runs an
  URL-driven search trigger (with a dedup ref against the form path).
  Direct URLs, cmd+click on history, and browser back/forward all
  reproduce the same fetch. "Recent searches" back-button and the
  history items are <Link>s; the bespoke resetView path is gone.

Also drops now-unused clearKeywordSearchParams,
navigateToBacklinksHistory/Tab helpers' last consumers, and the
PromptExplorerPage's onQueryChange/onSelectHistoryItem callbacks.

* format

* refactor: replace useMutation with manual state in keyword research

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-04 13:00:37 -04:00

58 lines
1.7 KiB
TypeScript

import { Link } from "@tanstack/react-router";
import { MessageSquare } from "lucide-react";
import {
HISTORY_ITEM_LINK_CLASS,
SearchHistorySection,
} from "@/client/features/ai-search/components/SearchHistorySection";
import { formatModelLabel } from "@/client/features/ai-search/platformLabels";
import type { PromptExplorerSearchHistoryItem } from "@/client/hooks/usePromptExplorerSearchHistory";
type Props = {
projectId: string;
history: PromptExplorerSearchHistoryItem[];
historyLoaded: boolean;
onRemoveHistoryItem: (timestamp: number) => void;
};
export function PromptExplorerHistorySection({ projectId, ...props }: Props) {
return (
<SearchHistorySection
{...props}
emptyIcon={MessageSquare}
emptyMessage="Enter a prompt to compare model answers"
noun="prompt"
renderItemLink={(item, content) => (
<Link
from="/p/$projectId/prompt-explorer"
to="/p/$projectId/prompt-explorer"
params={{ projectId }}
search={{
q: item.prompt,
models: item.models,
web: item.webSearch ? undefined : false,
cc:
item.webSearchCountryCode === "US"
? undefined
: item.webSearchCountryCode,
hb: item.highlightBrand || undefined,
}}
replace
className={HISTORY_ITEM_LINK_CLASS}
>
{content}
</Link>
)}
renderItem={(item) => (
<>
<p className="font-medium text-base-content truncate">
{item.prompt}
</p>
<p className="text-sm text-base-content/60 truncate">
{item.models.map(formatModelLabel).join(", ")}
</p>
</>
)}
/>
);
}