Organize/components/projects/projects-context.tsx

90 lines
2.6 KiB
TypeScript

"use client";
import { createContext, useContext, useState, useCallback } from "react";
import { toast } from "sonner";
import type { ProjectDTO } from "@/types/project";
import {
createProject,
renameProject as renameProjectAction,
deleteProject as deleteProjectAction,
} from "@/lib/actions/projects";
interface ProjectsContextValue {
projects: ProjectDTO[];
addProject: (title: string) => Promise<ProjectDTO | undefined>;
renameProject: (projectId: string, title: string) => Promise<void>;
removeProject: (projectId: string) => Promise<boolean>;
}
const ProjectsContext = createContext<ProjectsContextValue | null>(null);
/**
* Holds the signed-in user's project list, fetched once in the (app)
* layout and kept live from there -- the sidebar's expandable "Projects"
* section and the /projects page both read from this same instance, so
* creating/renaming/deleting a project in one place shows up in the other
* immediately, with no extra round-trip.
*/
export function ProjectsProvider({
initialProjects,
children,
}: {
initialProjects: ProjectDTO[];
children: React.ReactNode;
}) {
const [projects, setProjects] = useState(initialProjects);
const addProject = useCallback(async (title: string) => {
try {
const project = await createProject(title);
setProjects((prev) => [...prev, project]);
return project;
} catch {
toast.error("Couldn't create project. Try again.");
return undefined;
}
}, []);
const renameProject = useCallback(async (projectId: string, title: string) => {
let prevState: ProjectDTO[] = [];
setProjects((prev) => {
prevState = prev;
return prev.map((p) => (p.id === projectId ? { ...p, title } : p));
});
try {
await renameProjectAction(projectId, title);
} catch {
setProjects(prevState);
toast.error("Couldn't rename project. Try again.");
}
}, []);
const removeProject = useCallback(async (projectId: string) => {
let prevState: ProjectDTO[] = [];
setProjects((prev) => {
prevState = prev;
return prev.filter((p) => p.id !== projectId);
});
const result = await deleteProjectAction(projectId);
if (result?.error) {
setProjects(prevState);
toast.error(result.error);
return false;
}
return true;
}, []);
return (
<ProjectsContext.Provider value={{ projects, addProject, renameProject, removeProject }}>
{children}
</ProjectsContext.Provider>
);
}
export function useProjects() {
const ctx = useContext(ProjectsContext);
if (!ctx) throw new Error("useProjects must be used within a ProjectsProvider");
return ctx;
}