90 lines
2.6 KiB
TypeScript
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;
|
|
}
|