diff --git a/src/client/features/rank-tracking/RankTrackingDomainList.tsx b/src/client/features/rank-tracking/RankTrackingDomainList.tsx index 6a98a56..a352a11 100644 --- a/src/client/features/rank-tracking/RankTrackingDomainList.tsx +++ b/src/client/features/rank-tracking/RankTrackingDomainList.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { useNavigate } from "@tanstack/react-router"; import { LOCATIONS } from "@/client/features/keywords/locations"; import { AlertTriangle, Globe, Plus, ChevronRight } from "lucide-react"; import { getRankTrackingConfigSummaries } from "@/serverFunctions/rank-tracking"; @@ -13,13 +14,12 @@ type ConfigSummary = Awaited< export function RankTrackingDomainList({ projectId, - onSelectConfig, onAddDomain, }: { projectId: string; - onSelectConfig: (configId: string) => void; onAddDomain: () => void; }) { + const navigate = useNavigate(); const { data: summaries } = useQuery({ queryKey: ["rankTrackingConfigSummaries", projectId], queryFn: () => getRankTrackingConfigSummaries({ data: { projectId } }), @@ -31,7 +31,7 @@ export function RankTrackingDomainList({

Tracked Domains

- {(summaries ?? []).map((summary) => ( - onSelectConfig(summary.id)} - /> - ))} + {(summaries ?? []).length === 0 ? ( +
+
+ +
+

+ No tracked domains yet +

+

+ Add a domain to start monitoring keyword rankings over time. +

+
+ ) : ( + (summaries ?? []).map((summary) => ( + + void navigate({ + to: "/p/$projectId/rank-tracking/$configId", + params: { projectId, configId: summary.id }, + }) + } + /> + )) + )}
diff --git a/src/client/features/rank-tracking/RankTrackingPage.tsx b/src/client/features/rank-tracking/RankTrackingPage.tsx deleted file mode 100644 index 108368d..0000000 --- a/src/client/features/rank-tracking/RankTrackingPage.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useState } from "react"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { Loader2, TrendingUp } from "lucide-react"; -import { getRankTrackingConfigs } from "@/serverFunctions/rank-tracking"; -import type { RankTrackingConfig } from "@/types/schemas/rank-tracking"; -import { RankTrackingDomainList } from "./RankTrackingDomainList"; -import { RankTrackingDomainDetail } from "./RankTrackingDomainDetail"; -import { RankTrackingConfigModal } from "./RankTrackingConfigModal"; - -export function RankTrackingPage({ projectId }: { projectId: string }) { - const queryClient = useQueryClient(); - const [selectedConfigId, setSelectedConfigId] = useState(null); - const [showConfigModal, setShowConfigModal] = useState(false); - const [editingConfig, setEditingConfig] = useState( - null, - ); - - const { data: configs, isLoading } = useQuery({ - queryKey: ["rankTrackingConfigs", projectId], - queryFn: () => getRankTrackingConfigs({ data: { projectId } }), - }); - - const invalidateConfigs = () => { - void queryClient.invalidateQueries({ - queryKey: ["rankTrackingConfigs", projectId], - }); - void queryClient.invalidateQueries({ - queryKey: ["rankTrackingConfigSummaries", projectId], - }); - }; - - const selectedConfig = - configs?.find((c) => c.id === selectedConfigId) ?? null; - - const openAddModal = () => { - setEditingConfig(null); - setShowConfigModal(true); - }; - - const openEditModal = (config: RankTrackingConfig) => { - setEditingConfig(config); - setShowConfigModal(true); - }; - - return ( -
-
-
-

Rank Tracking

-

- Track keyword positions across domains -

-
- - {isLoading ? ( -
- -
- ) : !configs || configs.length === 0 ? ( -
-
- -
-

- Track your keyword positions over time -

-

- Monitor rankings for your saved keywords, see position changes, - and spot trends across desktop and mobile. -

- -

- Requires saved keywords in this project. -

-
- ) : selectedConfig ? ( - setSelectedConfigId(null)} - onEdit={() => openEditModal(selectedConfig)} - /> - ) : ( - - )} -
- - {showConfigModal && ( - setShowConfigModal(false)} - onSaved={() => { - setShowConfigModal(false); - invalidateConfigs(); - }} - /> - )} -
- ); -} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 25c457f..c5aeade 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -34,7 +34,9 @@ import { Route as ProjectPProjectIdDomainRouteImport } from './routes/_project/p import { Route as ProjectPProjectIdBacklinksRouteImport } from './routes/_project/p/$projectId/backlinks' import { Route as ProjectPProjectIdAuditRouteImport } from './routes/_project/p/$projectId/audit' import { Route as ProjectPProjectIdAiRouteImport } from './routes/_project/p/$projectId/ai' +import { Route as ProjectPProjectIdRankTrackingIndexRouteImport } from './routes/_project/p/$projectId/rank-tracking/index' import { Route as ProjectPProjectIdAuditIndexRouteImport } from './routes/_project/p/$projectId/audit/index' +import { Route as ProjectPProjectIdRankTrackingConfigIdRouteImport } from './routes/_project/p/$projectId/rank-tracking/$configId' import { Route as ProjectPProjectIdAuditIssuesResultIdRouteImport } from './routes/_project/p/$projectId/audit/issues/$resultId' const VerifyEmailRoute = VerifyEmailRouteImport.update({ @@ -161,12 +163,24 @@ const ProjectPProjectIdAiRoute = ProjectPProjectIdAiRouteImport.update({ path: '/ai', getParentRoute: () => ProjectPProjectIdRouteRoute, } as any) +const ProjectPProjectIdRankTrackingIndexRoute = + ProjectPProjectIdRankTrackingIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => ProjectPProjectIdRankTrackingRoute, + } as any) const ProjectPProjectIdAuditIndexRoute = ProjectPProjectIdAuditIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => ProjectPProjectIdAuditRoute, } as any) +const ProjectPProjectIdRankTrackingConfigIdRoute = + ProjectPProjectIdRankTrackingConfigIdRouteImport.update({ + id: '/$configId', + path: '/$configId', + getParentRoute: () => ProjectPProjectIdRankTrackingRoute, + } as any) const ProjectPProjectIdAuditIssuesResultIdRoute = ProjectPProjectIdAuditIssuesResultIdRouteImport.update({ id: '/issues/$resultId', @@ -193,10 +207,12 @@ export interface FileRoutesByFullPath { '/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute '/p/$projectId/domain': typeof ProjectPProjectIdDomainRoute '/p/$projectId/keywords': typeof ProjectPProjectIdKeywordsRoute - '/p/$projectId/rank-tracking': typeof ProjectPProjectIdRankTrackingRoute + '/p/$projectId/rank-tracking': typeof ProjectPProjectIdRankTrackingRouteWithChildren '/p/$projectId/saved': typeof ProjectPProjectIdSavedRoute '/p/$projectId/': typeof ProjectPProjectIdIndexRoute + '/p/$projectId/rank-tracking/$configId': typeof ProjectPProjectIdRankTrackingConfigIdRoute '/p/$projectId/audit/': typeof ProjectPProjectIdAuditIndexRoute + '/p/$projectId/rank-tracking/': typeof ProjectPProjectIdRankTrackingIndexRoute '/p/$projectId/audit/issues/$resultId': typeof ProjectPProjectIdAuditIssuesResultIdRoute } export interface FileRoutesByTo { @@ -216,10 +232,11 @@ export interface FileRoutesByTo { '/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute '/p/$projectId/domain': typeof ProjectPProjectIdDomainRoute '/p/$projectId/keywords': typeof ProjectPProjectIdKeywordsRoute - '/p/$projectId/rank-tracking': typeof ProjectPProjectIdRankTrackingRoute '/p/$projectId/saved': typeof ProjectPProjectIdSavedRoute '/p/$projectId': typeof ProjectPProjectIdIndexRoute + '/p/$projectId/rank-tracking/$configId': typeof ProjectPProjectIdRankTrackingConfigIdRoute '/p/$projectId/audit': typeof ProjectPProjectIdAuditIndexRoute + '/p/$projectId/rank-tracking': typeof ProjectPProjectIdRankTrackingIndexRoute '/p/$projectId/audit/issues/$resultId': typeof ProjectPProjectIdAuditIssuesResultIdRoute } export interface FileRoutesById { @@ -246,10 +263,12 @@ export interface FileRoutesById { '/_project/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute '/_project/p/$projectId/domain': typeof ProjectPProjectIdDomainRoute '/_project/p/$projectId/keywords': typeof ProjectPProjectIdKeywordsRoute - '/_project/p/$projectId/rank-tracking': typeof ProjectPProjectIdRankTrackingRoute + '/_project/p/$projectId/rank-tracking': typeof ProjectPProjectIdRankTrackingRouteWithChildren '/_project/p/$projectId/saved': typeof ProjectPProjectIdSavedRoute '/_project/p/$projectId/': typeof ProjectPProjectIdIndexRoute + '/_project/p/$projectId/rank-tracking/$configId': typeof ProjectPProjectIdRankTrackingConfigIdRoute '/_project/p/$projectId/audit/': typeof ProjectPProjectIdAuditIndexRoute + '/_project/p/$projectId/rank-tracking/': typeof ProjectPProjectIdRankTrackingIndexRoute '/_project/p/$projectId/audit/issues/$resultId': typeof ProjectPProjectIdAuditIssuesResultIdRoute } export interface FileRouteTypes { @@ -276,7 +295,9 @@ export interface FileRouteTypes { | '/p/$projectId/rank-tracking' | '/p/$projectId/saved' | '/p/$projectId/' + | '/p/$projectId/rank-tracking/$configId' | '/p/$projectId/audit/' + | '/p/$projectId/rank-tracking/' | '/p/$projectId/audit/issues/$resultId' fileRoutesByTo: FileRoutesByTo to: @@ -296,10 +317,11 @@ export interface FileRouteTypes { | '/p/$projectId/backlinks' | '/p/$projectId/domain' | '/p/$projectId/keywords' - | '/p/$projectId/rank-tracking' | '/p/$projectId/saved' | '/p/$projectId' + | '/p/$projectId/rank-tracking/$configId' | '/p/$projectId/audit' + | '/p/$projectId/rank-tracking' | '/p/$projectId/audit/issues/$resultId' id: | '__root__' @@ -328,7 +350,9 @@ export interface FileRouteTypes { | '/_project/p/$projectId/rank-tracking' | '/_project/p/$projectId/saved' | '/_project/p/$projectId/' + | '/_project/p/$projectId/rank-tracking/$configId' | '/_project/p/$projectId/audit/' + | '/_project/p/$projectId/rank-tracking/' | '/_project/p/$projectId/audit/issues/$resultId' fileRoutesById: FileRoutesById } @@ -521,6 +545,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectPProjectIdAiRouteImport parentRoute: typeof ProjectPProjectIdRouteRoute } + '/_project/p/$projectId/rank-tracking/': { + id: '/_project/p/$projectId/rank-tracking/' + path: '/' + fullPath: '/p/$projectId/rank-tracking/' + preLoaderRoute: typeof ProjectPProjectIdRankTrackingIndexRouteImport + parentRoute: typeof ProjectPProjectIdRankTrackingRoute + } '/_project/p/$projectId/audit/': { id: '/_project/p/$projectId/audit/' path: '/' @@ -528,6 +559,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProjectPProjectIdAuditIndexRouteImport parentRoute: typeof ProjectPProjectIdAuditRoute } + '/_project/p/$projectId/rank-tracking/$configId': { + id: '/_project/p/$projectId/rank-tracking/$configId' + path: '/$configId' + fullPath: '/p/$projectId/rank-tracking/$configId' + preLoaderRoute: typeof ProjectPProjectIdRankTrackingConfigIdRouteImport + parentRoute: typeof ProjectPProjectIdRankTrackingRoute + } '/_project/p/$projectId/audit/issues/$resultId': { id: '/_project/p/$projectId/audit/issues/$resultId' path: '/issues/$resultId' @@ -573,13 +611,31 @@ const ProjectPProjectIdAuditRouteWithChildren = ProjectPProjectIdAuditRouteChildren, ) +interface ProjectPProjectIdRankTrackingRouteChildren { + ProjectPProjectIdRankTrackingConfigIdRoute: typeof ProjectPProjectIdRankTrackingConfigIdRoute + ProjectPProjectIdRankTrackingIndexRoute: typeof ProjectPProjectIdRankTrackingIndexRoute +} + +const ProjectPProjectIdRankTrackingRouteChildren: ProjectPProjectIdRankTrackingRouteChildren = + { + ProjectPProjectIdRankTrackingConfigIdRoute: + ProjectPProjectIdRankTrackingConfigIdRoute, + ProjectPProjectIdRankTrackingIndexRoute: + ProjectPProjectIdRankTrackingIndexRoute, + } + +const ProjectPProjectIdRankTrackingRouteWithChildren = + ProjectPProjectIdRankTrackingRoute._addFileChildren( + ProjectPProjectIdRankTrackingRouteChildren, + ) + interface ProjectPProjectIdRouteRouteChildren { ProjectPProjectIdAiRoute: typeof ProjectPProjectIdAiRoute ProjectPProjectIdAuditRoute: typeof ProjectPProjectIdAuditRouteWithChildren ProjectPProjectIdBacklinksRoute: typeof ProjectPProjectIdBacklinksRoute ProjectPProjectIdDomainRoute: typeof ProjectPProjectIdDomainRoute ProjectPProjectIdKeywordsRoute: typeof ProjectPProjectIdKeywordsRoute - ProjectPProjectIdRankTrackingRoute: typeof ProjectPProjectIdRankTrackingRoute + ProjectPProjectIdRankTrackingRoute: typeof ProjectPProjectIdRankTrackingRouteWithChildren ProjectPProjectIdSavedRoute: typeof ProjectPProjectIdSavedRoute ProjectPProjectIdIndexRoute: typeof ProjectPProjectIdIndexRoute } @@ -591,7 +647,8 @@ const ProjectPProjectIdRouteRouteChildren: ProjectPProjectIdRouteRouteChildren = ProjectPProjectIdBacklinksRoute: ProjectPProjectIdBacklinksRoute, ProjectPProjectIdDomainRoute: ProjectPProjectIdDomainRoute, ProjectPProjectIdKeywordsRoute: ProjectPProjectIdKeywordsRoute, - ProjectPProjectIdRankTrackingRoute: ProjectPProjectIdRankTrackingRoute, + ProjectPProjectIdRankTrackingRoute: + ProjectPProjectIdRankTrackingRouteWithChildren, ProjectPProjectIdSavedRoute: ProjectPProjectIdSavedRoute, ProjectPProjectIdIndexRoute: ProjectPProjectIdIndexRoute, } diff --git a/src/routes/_project/p/$projectId/rank-tracking.tsx b/src/routes/_project/p/$projectId/rank-tracking.tsx index 17c65f5..ea32014 100644 --- a/src/routes/_project/p/$projectId/rank-tracking.tsx +++ b/src/routes/_project/p/$projectId/rank-tracking.tsx @@ -1,11 +1,22 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { RankTrackingPage } from "@/client/features/rank-tracking/RankTrackingPage"; +import { createFileRoute, Outlet } from "@tanstack/react-router"; export const Route = createFileRoute("/_project/p/$projectId/rank-tracking")({ - component: RankTrackingRoute, + component: RankTrackingLayout, }); -function RankTrackingRoute() { - const { projectId } = Route.useParams(); - return ; +function RankTrackingLayout() { + return ( +
+
+
+

Rank Tracking

+

+ Track keyword positions across domains +

+
+ + +
+
+ ); } diff --git a/src/routes/_project/p/$projectId/rank-tracking/$configId.tsx b/src/routes/_project/p/$projectId/rank-tracking/$configId.tsx new file mode 100644 index 0000000..a93f029 --- /dev/null +++ b/src/routes/_project/p/$projectId/rank-tracking/$configId.tsx @@ -0,0 +1,80 @@ +import { useState } from "react"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { getRankTrackingConfigs } from "@/serverFunctions/rank-tracking"; +import { RankTrackingDomainDetail } from "@/client/features/rank-tracking/RankTrackingDomainDetail"; +import { RankTrackingConfigModal } from "@/client/features/rank-tracking/RankTrackingConfigModal"; + +export const Route = createFileRoute( + "/_project/p/$projectId/rank-tracking/$configId", +)({ + component: RankTrackingConfigRoute, +}); + +function RankTrackingConfigRoute() { + const { projectId, configId } = Route.useParams(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [showConfigModal, setShowConfigModal] = useState(false); + + const { data: configs, isLoading } = useQuery({ + queryKey: ["rankTrackingConfigs", projectId], + queryFn: () => getRankTrackingConfigs({ data: { projectId } }), + }); + + const config = configs?.find((c) => c.id === configId) ?? null; + + const invalidateConfigs = () => { + void queryClient.invalidateQueries({ + queryKey: ["rankTrackingConfigs", projectId], + }); + void queryClient.invalidateQueries({ + queryKey: ["rankTrackingConfigSummaries", projectId], + }); + }; + + const handleBack = () => { + void navigate({ + to: "/p/$projectId/rank-tracking", + params: { projectId }, + }); + }; + + if (isLoading) return null; + + if (!config) { + return ( + <> +

+ Domain configuration not found. +

+ + + ); + } + + return ( + <> + setShowConfigModal(true)} + /> + + {showConfigModal && ( + setShowConfigModal(false)} + onSaved={() => { + setShowConfigModal(false); + invalidateConfigs(); + }} + /> + )} + + ); +} diff --git a/src/routes/_project/p/$projectId/rank-tracking/index.tsx b/src/routes/_project/p/$projectId/rank-tracking/index.tsx new file mode 100644 index 0000000..46d105b --- /dev/null +++ b/src/routes/_project/p/$projectId/rank-tracking/index.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; +import { createFileRoute } from "@tanstack/react-router"; +import { useQueryClient } from "@tanstack/react-query"; +import { RankTrackingDomainList } from "@/client/features/rank-tracking/RankTrackingDomainList"; +import { RankTrackingConfigModal } from "@/client/features/rank-tracking/RankTrackingConfigModal"; + +export const Route = createFileRoute("/_project/p/$projectId/rank-tracking/")({ + component: RankTrackingIndex, +}); + +function RankTrackingIndex() { + const { projectId } = Route.useParams(); + const queryClient = useQueryClient(); + const [showConfigModal, setShowConfigModal] = useState(false); + + const invalidateConfigs = () => { + void queryClient.invalidateQueries({ + queryKey: ["rankTrackingConfigs", projectId], + }); + void queryClient.invalidateQueries({ + queryKey: ["rankTrackingConfigSummaries", projectId], + }); + }; + + return ( + <> + setShowConfigModal(true)} + /> + + {showConfigModal && ( + setShowConfigModal(false)} + onSaved={() => { + setShowConfigModal(false); + invalidateConfigs(); + }} + /> + )} + + ); +}