← back to Open Seo
src/client/features/projects/ProjectSwitcher.tsx
111 lines
import { Link, useNavigate } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query";
import { Check, ChevronsUpDown, FolderCog } from "lucide-react";
import { getProjects } from "@/serverFunctions/projects";
import { setLastProjectId } from "@/client/lib/active-project";
import { closeDropdown } from "@/client/lib/dropdown";
import type { ProjectSummary } from "./types";
export function ProjectSwitcher({
activeProjectId,
onCloseDrawer,
}: {
activeProjectId: string | null;
// Mobile sidebar passes this so switching / navigating away also closes the
// drawer overlay.
onCloseDrawer?: () => void;
}) {
const navigate = useNavigate();
const projectsQuery = useQuery({
queryKey: ["projects"],
queryFn: () => getProjects(),
});
const projects = projectsQuery.data ?? [];
const activeProject =
projects.find((project) => project.id === activeProjectId) ?? null;
const handleSelect = (project: ProjectSummary) => {
closeDropdown();
onCloseDrawer?.();
if (project.id === activeProjectId) return;
setLastProjectId(project.id);
void navigate({
to: "/p/$projectId",
params: { projectId: project.id },
});
};
return (
<div className="dropdown w-full">
<button
type="button"
tabIndex={0}
aria-label="Switch project"
className="flex w-full items-center justify-between gap-2 rounded-lg border border-base-300 bg-base-100 px-3 py-1.5 text-left transition-colors hover:border-base-content/25"
>
<span className="flex min-w-0 flex-col">
<span className="truncate text-sm font-medium text-base-content">
{activeProject?.name ?? "Select project"}
</span>
{activeProject?.domain ? (
<span className="truncate text-xs font-normal text-base-content/50">
{activeProject.domain}
</span>
) : null}
</span>
<ChevronsUpDown className="size-3.5 shrink-0 text-base-content/40" />
</button>
<ul
tabIndex={0}
className="dropdown-content z-30 menu w-full rounded-box border border-base-300 bg-base-100 p-2 shadow-lg"
>
{projects.map((project) => {
const isActive = project.id === activeProjectId;
return (
<li key={project.id}>
<button
type="button"
onClick={() => handleSelect(project)}
className={isActive ? "active" : ""}
>
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate">{project.name}</span>
{project.domain ? (
<span className="truncate text-xs text-base-content/50">
{project.domain}
</span>
) : null}
</span>
{isActive ? (
<Check className="size-4 shrink-0 text-primary" />
) : null}
</button>
</li>
);
})}
{projects.length > 0 ? (
<li
aria-hidden="true"
className="pointer-events-none my-1 h-px bg-base-300 p-0"
/>
) : null}
<li>
<Link
to="/projects"
onClick={() => {
closeDropdown();
onCloseDrawer?.();
}}
>
<FolderCog className="size-4" />
Manage projects
</Link>
</li>
</ul>
</div>
);
}