Ben Senescu 8405564925
feat: Add Backlinks page (#23)
* feat: add backlinks setup and overview workflow

* save

* fix backlinks timeseries and refine overview layout

* update backlinks table guidance

* refactor backlinks CI cleanup

* refactor backlinks page state colocation

* fix backlinks access error handling

Differentiate DataForSEO subscription access failures from billing issues and preserve exact page hosts for page-level backlink lookups.

* fix backlinks project scoping and error states

* simplify backlinks billing and lazy loading

* harden backlinks profiling and access flow

* refine backlinks access status typing

* update backlinks scope selection and access handling

* fix backlinks scope fallback and cache scoping

* fix backlinks ci checks

* docs: simplify backlinks pricing

* refine backlinks search: move scope toggle below input, remove redundant description

* refine backlinks summaries and help text

* refine backlinks table sorting

* fix backlinks invalid target handling

* delete docs

* fix backlinks scope inference and domain normalization

* docs: mark backlinks as a supported workflow

* fix backlinks links badge wrapping

* test: split backlinks test suites
2026-03-16 17:48:13 -04:00

93 lines
3.1 KiB
TypeScript

import { BacklinksSearchCard } from "./BacklinksSearchCard";
import { BacklinksBody } from "./BacklinksPageContent";
import type { BacklinksPageProps } from "./backlinksPageTypes";
import {
navigateToBacklinksSearch,
navigateToBacklinksTab,
useBacklinksPageData,
} from "./useBacklinksPageData";
import { getStandardErrorMessage } from "@/client/lib/error-messages";
export function BacklinksPage({
projectId,
searchState,
navigate,
}: BacklinksPageProps) {
const {
accessStatus,
accessStatusErrorMessage,
accessStatusQuery,
activeTabErrorMessage,
backlinksDisabledByError,
backlinksEnabled,
overviewErrorMessage,
overviewQuery,
referringDomainsQuery,
searchCardInitialValues,
testAccessMutation,
topPagesQuery,
} = useBacklinksPageData({ projectId, searchState });
return (
<div className="px-4 py-4 pb-24 overflow-auto md:px-6 md:py-6 md:pb-8">
<div className="mx-auto max-w-7xl space-y-4">
<div>
<h1 className="text-2xl font-semibold">Backlinks</h1>
<p className="text-sm text-base-content/70">
Understand who links to a site, what changed recently, and which
pages attract links.
</p>
</div>
{!accessStatusQuery.isLoading &&
backlinksEnabled &&
!backlinksDisabledByError ? (
<BacklinksSearchCard
errorMessage={overviewErrorMessage}
initialValues={searchCardInitialValues}
isFetching={
overviewQuery.isFetching ||
referringDomainsQuery.isFetching ||
topPagesQuery.isFetching
}
onSubmit={(values) => navigateToBacklinksSearch(navigate, values)}
/>
) : null}
<BacklinksBody
accessStatus={accessStatus}
accessStatusError={accessStatusErrorMessage}
backlinksDisabledByError={backlinksDisabledByError}
backlinksEnabled={backlinksEnabled}
isAccessStatusLoading={accessStatusQuery.isLoading}
overviewData={overviewQuery.data}
overviewError={overviewErrorMessage}
overviewLoading={overviewQuery.isLoading}
referringDomains={referringDomainsQuery.data}
searchState={searchState}
tabErrorMessage={activeTabErrorMessage}
tabLoading={
(searchState.tab === "domains" &&
referringDomainsQuery.isLoading) ||
(searchState.tab === "pages" && topPagesQuery.isLoading)
}
testError={
testAccessMutation.error
? getStandardErrorMessage(
testAccessMutation.error,
"Could not test Backlinks access.",
)
: null
}
testIsPending={testAccessMutation.isPending}
topPages={topPagesQuery.data}
onRetryAccess={() => void accessStatusQuery.refetch()}
onSetActiveTab={(tab) => navigateToBacklinksTab(navigate, tab)}
onRetryOverview={() => void overviewQuery.refetch()}
onTestAccess={() => testAccessMutation.mutate()}
/>
</div>
</div>
);
}