import * as React from "react"; import { Link, createFileRoute } from "@tanstack/react-router"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ChevronRight, Plus } from "lucide-react"; import { toast } from "sonner"; import { getArchivedProjects, getProjects, restoreProject, } from "@/serverFunctions/projects"; import { getStandardErrorMessage } from "@/client/lib/error-messages"; import { getLastProjectId } from "@/client/lib/active-project"; import { CreateProjectModal } from "@/client/features/projects/CreateProjectModal"; export const Route = createFileRoute("/_app/projects")({ component: ProjectsPage, }); function ProjectsPage() { const [creating, setCreating] = React.useState(false); // Read after mount to keep SSR/first render stable. const [currentProjectId, setCurrentProjectId] = React.useState( null, ); React.useEffect(() => { setCurrentProjectId(getLastProjectId()); }, []); const projectsQuery = useQuery({ queryKey: ["projects"], queryFn: () => getProjects(), }); const projects = projectsQuery.data ?? []; return (

Projects

Each project has its own Search Console, rank tracking, and audits.

{projectsQuery.isLoading ? (
) : (
    {projects.map((project) => (
  • {project.name} {project.id === currentProjectId ? ( Current ) : null} {project.domain ?? "No domain set"}
  • ))}
)}
{creating ? ( setCreating(false)} /> ) : null}
); } function ArchivedProjects() { const queryClient = useQueryClient(); const archivedQuery = useQuery({ queryKey: ["projects", "archived"], queryFn: () => getArchivedProjects(), }); const archived = archivedQuery.data ?? []; const restoreMutation = useMutation({ mutationFn: (projectId: string) => restoreProject({ data: { archivedProjectId: projectId } }), onSuccess: async () => { // Prefix match invalidates both the active and archived lists. await queryClient.invalidateQueries({ queryKey: ["projects"] }); toast.success("Project restored"); }, onError: (error) => toast.error(getStandardErrorMessage(error, "Failed to restore project")), }); if (archived.length === 0) return null; return (

Archived

); }