metatron-open-seo/src/client/features/backlinks/BacklinksPageContent.tsx
Ben Senescu 45234e6945
Fix backlink tab states (#218)
* Fix tab active state wiring across result views

* Add spam filters to referring domains
2026-05-22 22:49:05 -04:00

213 lines
5.3 KiB
TypeScript

import { useMemo } from "react";
import {
BacklinksOverviewPanels,
BacklinksResultsCard,
} from "./BacklinksPageSections";
import {
BacklinksAccessLoadingState,
BacklinksErrorState,
BacklinksLoadingState,
BacklinksSetupGate,
} from "./BacklinksPageStates";
import { BacklinksHistorySection } from "./BacklinksHistorySection";
import type { BacklinksSearchHistoryItem } from "@/client/hooks/useBacklinksSearchHistory";
import type {
BacklinksOverviewData,
BacklinksReferringDomainsData,
BacklinksSearchState,
BacklinksTopPagesData,
} from "./backlinksPageTypes";
import type { UseAccessGateResult } from "@/client/features/access-gate/useAccessGate";
import { buildSummaryStats } from "./backlinksPageUtils";
import {
filterBacklinkRows,
filterReferringDomainRows,
filterTopPageRows,
} from "./backlinksFiltering";
import type { BacklinksFiltersState } from "./useBacklinksFilters";
import {
SearchTabStrip,
type SearchTab,
} from "@/client/features/search-tabs/SearchTabStrip";
type BacklinksBodyProps = {
projectId: string;
accessGate: UseAccessGateResult;
backlinksDisabledByError: boolean;
history: BacklinksSearchHistoryItem[];
historyLoaded: boolean;
overviewData: BacklinksOverviewData | undefined;
overviewError: string | null;
overviewLoading: boolean;
referringDomains: BacklinksReferringDomainsData | undefined;
searchState: BacklinksSearchState;
filters: BacklinksFiltersState;
tabErrorMessage: string | null;
tabLoading: boolean;
topPages: BacklinksTopPagesData | undefined;
onRemoveHistoryItem: (timestamp: number) => void;
onRetryOverview: () => void;
onTabChange: (tab: BacklinksSearchState["tab"]) => void;
searchTabs: {
activeTabId: string | null;
tabs: SearchTab[];
onSelect: (tab: SearchTab) => void;
onClose: (tabId: string) => void;
onViewed: (tabId: string, when?: number) => void;
} | null;
};
export function BacklinksBody({
projectId,
accessGate,
backlinksDisabledByError,
history,
historyLoaded,
overviewData,
overviewError,
overviewLoading,
referringDomains,
searchState,
filters,
tabErrorMessage,
tabLoading,
topPages,
onRemoveHistoryItem,
onRetryOverview,
onTabChange,
searchTabs,
}: BacklinksBodyProps) {
const mergedData = useMemo(
() => mergeTabData(overviewData, referringDomains, topPages),
[overviewData, referringDomains, topPages],
);
const filteredData = useMemo(() => {
if (!mergedData) {
return { backlinks: [], referringDomains: [], topPages: [] };
}
return {
backlinks: filterBacklinkRows(
mergedData.backlinks,
filters.backlinks.values,
),
referringDomains: filterReferringDomainRows(
mergedData.referringDomains,
filters.domains.values,
),
topPages: filterTopPageRows(mergedData.topPages, filters.pages.values),
};
}, [
mergedData,
filters.backlinks.values,
filters.domains.values,
filters.pages.values,
]);
const summaryStats = useMemo(
() => buildSummaryStats(mergedData),
[mergedData],
);
const tabStrip = searchTabs ? (
<SearchTabStrip
projectId={projectId}
activeTabId={searchTabs.activeTabId}
tabs={searchTabs.tabs}
onSelect={searchTabs.onSelect}
onClose={searchTabs.onClose}
onViewed={searchTabs.onViewed}
/>
) : null;
if (accessGate.isLoading) {
return <BacklinksAccessLoadingState />;
}
if (accessGate.statusErrorMessage) {
return (
<BacklinksErrorState
errorMessage={accessGate.statusErrorMessage}
onRetry={accessGate.onRetry}
/>
);
}
if (!accessGate.enabled || backlinksDisabledByError) {
return (
<BacklinksSetupGate
errorMessage={accessGate.errorMessage}
isRefetching={accessGate.isRefetching}
onRetry={accessGate.onRetry}
/>
);
}
if (!searchState.target) {
return (
<BacklinksHistorySection
projectId={projectId}
history={history}
historyLoaded={historyLoaded}
onRemoveHistoryItem={onRemoveHistoryItem}
/>
);
}
if (overviewLoading) {
return (
<>
{tabStrip}
<BacklinksLoadingState />
</>
);
}
if (!mergedData) {
return (
<>
{tabStrip}
<BacklinksErrorState
errorMessage={overviewError}
onRetry={onRetryOverview}
/>
</>
);
}
return (
<>
{tabStrip}
<BacklinksOverviewPanels
projectId={projectId}
data={mergedData}
summaryStats={summaryStats}
/>
<BacklinksResultsCard
activeTab={searchState.tab}
filteredData={filteredData}
filters={filters}
isTabLoading={searchState.tab !== "backlinks" && tabLoading}
tabErrorMessage={
searchState.tab !== "backlinks" ? tabErrorMessage : null
}
exportTarget={mergedData.displayTarget || searchState.target}
onTabChange={onTabChange}
/>
</>
);
}
function mergeTabData(
data: BacklinksOverviewData | undefined,
referringDomains: BacklinksReferringDomainsData | undefined,
topPages: BacklinksTopPagesData | undefined,
) {
if (!data) {
return undefined;
}
return {
...data,
referringDomains: referringDomains ?? data.referringDomains,
topPages: topPages ?? data.topPages,
};
}