* Fix tab active state wiring across result views * Add spam filters to referring domains
213 lines
5.3 KiB
TypeScript
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,
|
|
};
|
|
}
|