diff --git a/src/routes/p/$projectId/ai.tsx b/src/routes/_project/p/$projectId/ai.tsx
similarity index 98%
rename from src/routes/p/$projectId/ai.tsx
rename to src/routes/_project/p/$projectId/ai.tsx
index d86df06..de13359 100644
--- a/src/routes/p/$projectId/ai.tsx
+++ b/src/routes/_project/p/$projectId/ai.tsx
@@ -6,7 +6,7 @@ const SUPPORT_EMAIL = "ben@everyapp.com";
const DATAFORSEO_MCP_DOCS_URL =
"https://dataforseo.com/help-center/setting-up-the-official-dataforseo-mcp-server-simple-guide";
-export const Route = createFileRoute("/p/$projectId/ai")({
+export const Route = createFileRoute("/_project/p/$projectId/ai")({
component: AiPage,
});
diff --git a/src/routes/p/$projectId/audit.tsx b/src/routes/_project/p/$projectId/audit.tsx
similarity index 68%
rename from src/routes/p/$projectId/audit.tsx
rename to src/routes/_project/p/$projectId/audit.tsx
index 363bee0..315b64e 100644
--- a/src/routes/p/$projectId/audit.tsx
+++ b/src/routes/_project/p/$projectId/audit.tsx
@@ -1,6 +1,6 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";
-export const Route = createFileRoute("/p/$projectId/audit")({
+export const Route = createFileRoute("/_project/p/$projectId/audit")({
component: SiteAuditLayout,
});
diff --git a/src/routes/p/$projectId/audit/index.tsx b/src/routes/_project/p/$projectId/audit/index.tsx
similarity index 98%
rename from src/routes/p/$projectId/audit/index.tsx
rename to src/routes/_project/p/$projectId/audit/index.tsx
index ec13749..d479795 100644
--- a/src/routes/p/$projectId/audit/index.tsx
+++ b/src/routes/_project/p/$projectId/audit/index.tsx
@@ -19,8 +19,8 @@ import {
SUPPORT_URL,
} from "@/client/features/audit/shared";
-export const Route = createFileRoute<"/p/$projectId/audit/">(
- "/p/$projectId/audit/",
+export const Route = createFileRoute<"/_project/p/$projectId/audit/">(
+ "/_project/p/$projectId/audit/",
)({
validateSearch: auditSearchSchema,
component: SiteAuditPage,
diff --git a/src/routes/p/$projectId/audit/issues/$resultId.tsx b/src/routes/_project/p/$projectId/audit/issues/$resultId.tsx
similarity index 90%
rename from src/routes/p/$projectId/audit/issues/$resultId.tsx
rename to src/routes/_project/p/$projectId/audit/issues/$resultId.tsx
index ea79843..0978313 100644
--- a/src/routes/p/$projectId/audit/issues/$resultId.tsx
+++ b/src/routes/_project/p/$projectId/audit/issues/$resultId.tsx
@@ -2,7 +2,9 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { PsiIssuesScreen } from "@/client/features/psi/issues/PsiIssuesScreen";
import { psiIssuesSearchSchema } from "@/types/schemas/psi";
-export const Route = createFileRoute("/p/$projectId/audit/issues/$resultId")({
+export const Route = createFileRoute(
+ "/_project/p/$projectId/audit/issues/$resultId",
+)({
validateSearch: psiIssuesSearchSchema,
component: AuditIssuesPage,
});
diff --git a/src/routes/p/$projectId/backlinks.tsx b/src/routes/_project/p/$projectId/backlinks.tsx
similarity index 93%
rename from src/routes/p/$projectId/backlinks.tsx
rename to src/routes/_project/p/$projectId/backlinks.tsx
index 1c71bec..6b0240d 100644
--- a/src/routes/p/$projectId/backlinks.tsx
+++ b/src/routes/_project/p/$projectId/backlinks.tsx
@@ -3,7 +3,7 @@ import { BacklinksPage } from "@/client/features/backlinks/BacklinksPage";
import { inferBacklinksSearchScopeFromTarget } from "@/client/features/backlinks/backlinksSearchScope";
import { backlinksSearchSchema } from "@/types/schemas/backlinks";
-export const Route = createFileRoute("/p/$projectId/backlinks")({
+export const Route = createFileRoute("/_project/p/$projectId/backlinks")({
validateSearch: backlinksSearchSchema,
component: BacklinksRoute,
});
diff --git a/src/routes/p/$projectId/domain.tsx b/src/routes/_project/p/$projectId/domain.tsx
similarity index 93%
rename from src/routes/p/$projectId/domain.tsx
rename to src/routes/_project/p/$projectId/domain.tsx
index 54db3db..8b9ed1f 100644
--- a/src/routes/p/$projectId/domain.tsx
+++ b/src/routes/_project/p/$projectId/domain.tsx
@@ -7,7 +7,7 @@ import {
} from "@/client/features/domain/utils";
import { domainSearchSchema } from "@/types/schemas/domain";
-export const Route = createFileRoute("/p/$projectId/domain")({
+export const Route = createFileRoute("/_project/p/$projectId/domain")({
validateSearch: domainSearchSchema,
component: DomainOverviewRoute,
});
diff --git a/src/routes/p/$projectId/index.tsx b/src/routes/_project/p/$projectId/index.tsx
similarity index 77%
rename from src/routes/p/$projectId/index.tsx
rename to src/routes/_project/p/$projectId/index.tsx
index 88acf6d..8a24560 100644
--- a/src/routes/p/$projectId/index.tsx
+++ b/src/routes/_project/p/$projectId/index.tsx
@@ -1,6 +1,6 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
-export const Route = createFileRoute("/p/$projectId/")({
+export const Route = createFileRoute("/_project/p/$projectId/")({
beforeLoad: ({ params }) => {
throw redirect({
to: "/p/$projectId/keywords",
diff --git a/src/routes/p/$projectId/keywords.tsx b/src/routes/_project/p/$projectId/keywords.tsx
similarity index 95%
rename from src/routes/p/$projectId/keywords.tsx
rename to src/routes/_project/p/$projectId/keywords.tsx
index 4d198cc..d51d7de 100644
--- a/src/routes/p/$projectId/keywords.tsx
+++ b/src/routes/_project/p/$projectId/keywords.tsx
@@ -10,7 +10,7 @@ import {
} from "@/client/features/keywords/keywordSearchParams";
import { keywordsSearchSchema } from "@/types/schemas/keywords";
-export const Route = createFileRoute("/p/$projectId/keywords")({
+export const Route = createFileRoute("/_project/p/$projectId/keywords")({
validateSearch: keywordsSearchSchema,
beforeLoad: ({ params, search }) => {
const { normalized, changed } = normalizeLegacyKeywordSearch(search);
diff --git a/src/routes/_project/p/$projectId/route.tsx b/src/routes/_project/p/$projectId/route.tsx
new file mode 100644
index 0000000..6701b29
--- /dev/null
+++ b/src/routes/_project/p/$projectId/route.tsx
@@ -0,0 +1,48 @@
+import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
+import { getErrorCode } from "@/client/lib/error-messages";
+import { AuthenticatedAppLayout } from "@/client/layout/AppShell";
+import {
+ getCurrentAuthRedirectFromHref,
+ getSignInSearch,
+} from "@/lib/auth-redirect";
+import { getProjectAccess } from "@/serverFunctions/projects";
+
+export const Route = createFileRoute("/_project/p/$projectId")({
+ beforeLoad: async ({ location, params }) => {
+ try {
+ await getProjectAccess({ data: { projectId: params.projectId } });
+ } catch (error) {
+ if (getErrorCode(error) === "UNAUTHENTICATED") {
+ throw redirect({
+ to: "/sign-in",
+ search: getSignInSearch(
+ getCurrentAuthRedirectFromHref(location.href),
+ ),
+ replace: true,
+ });
+ }
+
+ throw redirect({ to: "/", replace: true });
+ }
+ },
+ pendingComponent: ProjectRoutePending,
+ component: ProjectLayout,
+});
+
+function ProjectLayout() {
+ const { projectId } = Route.useParams();
+
+ return (
+
+
+
+ );
+}
+
+function ProjectRoutePending() {
+ return (
+
+
+
+ );
+}
diff --git a/src/routes/p/$projectId/saved.tsx b/src/routes/_project/p/$projectId/saved.tsx
similarity index 99%
rename from src/routes/p/$projectId/saved.tsx
rename to src/routes/_project/p/$projectId/saved.tsx
index b6cba1d..cef4791 100644
--- a/src/routes/p/$projectId/saved.tsx
+++ b/src/routes/_project/p/$projectId/saved.tsx
@@ -10,7 +10,7 @@ import { Trash2, Download, Search, Loader2, AlertCircle } from "lucide-react";
import { buildCsv, downloadCsv } from "@/client/lib/csv";
import { getStandardErrorMessage } from "@/client/lib/error-messages";
-export const Route = createFileRoute("/p/$projectId/saved")({
+export const Route = createFileRoute("/_project/p/$projectId/saved")({
component: SavedKeywordsPage,
});
diff --git a/src/routes/_project/route.tsx b/src/routes/_project/route.tsx
new file mode 100644
index 0000000..9b64130
--- /dev/null
+++ b/src/routes/_project/route.tsx
@@ -0,0 +1,9 @@
+import { Outlet, createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/_project")({
+ component: ProjectRouteLayout,
+});
+
+function ProjectRouteLayout() {
+ return
;
+}
diff --git a/src/routes/p/$projectId/route.tsx b/src/routes/p/$projectId/route.tsx
deleted file mode 100644
index 7403a32..0000000
--- a/src/routes/p/$projectId/route.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { createFileRoute, Outlet, useNavigate } from "@tanstack/react-router";
-import { useEffect } from "react";
-import { useQuery } from "@tanstack/react-query";
-import { getProjectAccess } from "@/serverFunctions/projects";
-
-export const Route = createFileRoute("/p/$projectId")({
- component: ProjectLayout,
-});
-
-function ProjectLayout() {
- const params = Route.useParams();
- const { projectId } = params;
- const navigate = useNavigate();
-
- const {
- data: project,
- isLoading,
- isError,
- } = useQuery({
- queryKey: ["project", projectId],
- queryFn: () => getProjectAccess({ data: { projectId } }),
- });
-
- useEffect(() => {
- if (!isLoading && (isError || !project)) {
- void navigate({ to: "/" });
- }
- }, [isLoading, isError, project, navigate]);
-
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- if (!project) return null;
-
- return
;
-}