diff --git a/client/src/hooks/useProject.ts b/client/src/hooks/useProject.ts
new file mode 100644
index 0000000..4e9a9ad
--- /dev/null
+++ b/client/src/hooks/useProject.ts
@@ -0,0 +1,12 @@
+import { useContext } from "react";
+import { ProjectContext } from "../providers/ProjectProvider";
+
+export const useProject = () => {
+ const context = useContext(ProjectContext);
+
+ if (context === null) {
+ throw new Error("You probably forgot to put .");
+ }
+
+ return context;
+};
diff --git a/client/src/pages/Projects/Projects.tsx b/client/src/pages/Projects/Projects.tsx
index 7d3abad..3457d2f 100644
--- a/client/src/pages/Projects/Projects.tsx
+++ b/client/src/pages/Projects/Projects.tsx
@@ -14,12 +14,17 @@ type APIProjectList = {
export const Projects = () => {
const { token } = useAuth();
const fetcher = createSWRFetcher(token);
- const { data } = useSWR("projects", fetcher);
+ const { data, mutate } = useSWR("projects", fetcher);
return (
-
+
{data?.data.map((project) => (
-
+
))}
);
diff --git a/client/src/pages/Projects/components/AddTask.tsx b/client/src/pages/Projects/components/AddTask.tsx
index a097d16..a4434e5 100644
--- a/client/src/pages/Projects/components/AddTask.tsx
+++ b/client/src/pages/Projects/components/AddTask.tsx
@@ -2,31 +2,26 @@ import AddTaskIcon from "@mui/icons-material/AddTask";
import { Box, Button, TextField, Toolbar } from "@mui/material";
import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
-import { KeyedMutator } from "swr";
import { useAuth } from "../../../hooks/useAuth";
-import { APIProjectTasksList } from "./Project";
+import { useProject } from "../../../hooks/useProject";
type NewTaskForm = {
description: string;
};
-export type AddTaskProps = {
- projectId: number;
- mutate: KeyedMutator;
-};
-
-export const AddTask = ({ projectId, mutate }: AddTaskProps) => {
+export const AddTask = () => {
const [isLoading, setIsLoading] = useState(false);
const { apiClient } = useAuth();
const { register, handleSubmit, reset } = useForm();
+ const { tasksMutate, project } = useProject();
const onSubmit: SubmitHandler = (data) => {
setIsLoading(true);
- apiClient(`projects/${projectId}/tasks`, {
+ apiClient(`projects/${project.id}/tasks`, {
method: "POST",
body: JSON.stringify(data),
}).then(() => {
- mutate();
+ tasksMutate();
reset();
setIsLoading(false);
});
diff --git a/client/src/pages/Projects/components/DeleteProjectDialog.tsx b/client/src/pages/Projects/components/DeleteProjectDialog.tsx
new file mode 100644
index 0000000..449d290
--- /dev/null
+++ b/client/src/pages/Projects/components/DeleteProjectDialog.tsx
@@ -0,0 +1,58 @@
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle
+} from "@mui/material";
+import { SetStateAction, useState } from "react";
+import { useAuth } from "../../../hooks/useAuth";
+import { useProject } from "../../../hooks/useProject";
+
+export type DeleteProjectDialogProps = {
+ open: boolean;
+ setOpen: (value: SetStateAction) => void;
+};
+
+export const DeleteProjectDialog = ({
+ open,
+ setOpen,
+}: DeleteProjectDialogProps) => {
+ const [isLoading, setIsLoading] = useState(false);
+ const { project, projectMutate } = useProject();
+ const { apiClient } = useAuth();
+
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const handleDelete = () => {
+ setIsLoading(true);
+
+ apiClient(`projects/${project.id}`, {
+ method: "DELETE",
+ }).then(() => {
+ projectMutate();
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/client/src/pages/Projects/components/Project.tsx b/client/src/pages/Projects/components/Project.tsx
index 0b00481..1804269 100644
--- a/client/src/pages/Projects/components/Project.tsx
+++ b/client/src/pages/Projects/components/Project.tsx
@@ -1,5 +1,4 @@
import { Add } from "@mui/icons-material";
-import MoreVertIcon from "@mui/icons-material/MoreVert";
import {
Card,
CardActions,
@@ -9,9 +8,12 @@ import {
} from "@mui/material";
import useSWR from "swr";
import { useAuth } from "../../../hooks/useAuth";
+import { ProjectProvider } from "../../../providers/ProjectProvider";
import { createSWRFetcher } from "../../../utils/swrFetcher";
-import { TaskListProps, TasksList } from "./TasksList";
import { AddTask } from "./AddTask";
+import { ProjectOptions } from "./ProjectOptions";
+import { TaskListProps, TasksList } from "./TasksList";
+
export type APIProjectTasksList = {
data: TaskListProps["tasks"];
};
@@ -19,6 +21,7 @@ export type APIProjectTasksList = {
export type ProjectProps = {
id: number;
name: string;
+ projectMutate: Function;
};
export const Project = (props: ProjectProps) => {
@@ -34,35 +37,24 @@ export const Project = (props: ProjectProps) => {
const completedTasks = data?.data.filter((task) => task.finishedAt) ?? [];
return (
-
-
-
+
+
+ } title={props.name} />
+
+
+
+
+
+
+
+
- }
- title={props.name}
- />
-
-
-
-
-
-
-
-
-
-
-
+
+
+
);
};
diff --git a/client/src/pages/Projects/components/ProjectOptions.tsx b/client/src/pages/Projects/components/ProjectOptions.tsx
new file mode 100644
index 0000000..1e9b94e
--- /dev/null
+++ b/client/src/pages/Projects/components/ProjectOptions.tsx
@@ -0,0 +1,56 @@
+import MoreVertIcon from "@mui/icons-material/MoreVert";
+import { Divider, IconButton, Menu, MenuItem } from "@mui/material";
+import { useState } from "react";
+import { DeleteProjectDialog } from "./DeleteProjectDialog";
+import { RenameProjectDialog } from "./RenameProjectDialog";
+
+export const ProjectOptions = () => {
+ const [renameDialogOpen, setRenameDialogOpen] = useState(false);
+ const [deleteDialogOption, setDeleteDialogOption] = useState(false);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ const handleRenameDialogOpen = () => {
+ handleClose();
+ setRenameDialogOpen(true);
+ };
+
+ const handleDeleteDialogOpen = () => {
+ handleClose();
+ setDeleteDialogOption(true);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/client/src/pages/Projects/components/RenameProjectDialog.tsx b/client/src/pages/Projects/components/RenameProjectDialog.tsx
new file mode 100644
index 0000000..ae07dcc
--- /dev/null
+++ b/client/src/pages/Projects/components/RenameProjectDialog.tsx
@@ -0,0 +1,81 @@
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ TextField,
+} from "@mui/material";
+import { SetStateAction, useState } from "react";
+import { SubmitHandler, useForm } from "react-hook-form";
+import { useAuth } from "../../../hooks/useAuth";
+import { useProject } from "../../../hooks/useProject";
+
+type RenameProjectForm = {
+ name: string;
+};
+
+export type RenameProjectDialogProps = {
+ open: boolean;
+ setOpen: (value: SetStateAction) => void;
+};
+
+export const RenameProjectDialog = ({
+ open,
+ setOpen,
+}: RenameProjectDialogProps) => {
+ const [isLoading, setIsLoading] = useState(false);
+ const { apiClient } = useAuth();
+ const { projectMutate, project } = useProject();
+ const { register, handleSubmit, reset } = useForm({
+ defaultValues: {
+ name: project.name,
+ },
+ });
+
+ const handleClose = () => {
+ reset();
+ setOpen(false);
+ };
+
+ const onSubmit: SubmitHandler = (data) => {
+ setIsLoading(true);
+
+ apiClient(`projects/${project.id}`, {
+ method: "PUT",
+ body: JSON.stringify({
+ name: data.name,
+ }),
+ }).then(() => {
+ projectMutate();
+ setIsLoading(false);
+ handleClose();
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/client/src/pages/Projects/components/TasksList.tsx b/client/src/pages/Projects/components/TasksList.tsx
index 3002711..c7abe82 100644
--- a/client/src/pages/Projects/components/TasksList.tsx
+++ b/client/src/pages/Projects/components/TasksList.tsx
@@ -1,34 +1,28 @@
import { List, ListSubheader } from "@mui/material";
-import { KeyedMutator } from "swr";
import { useAuth } from "../../../hooks/useAuth";
-import { APIProjectTasksList } from "./Project";
+import { useProject } from "../../../hooks/useProject";
import { Task, TasksListItem } from "./TasksListItem";
export type TaskListProps = {
- projectId: number;
title: string;
tasks: Task[];
- mutate: KeyedMutator;
};
-export const TasksList = ({
- projectId,
- title,
- tasks,
- mutate,
-}: TaskListProps) => {
+export const TasksList = ({ title, tasks }: TaskListProps) => {
const { apiClient } = useAuth();
+ const { tasksMutate, project } = useProject();
+
const handleCheck = (taskId: number) => {
- apiClient(`projects/${projectId}/tasks/${taskId}/finish`).then((res) => {
- mutate();
+ apiClient(`projects/${project.id}/tasks/${taskId}/finish`).then((res) => {
+ tasksMutate();
});
};
const handleDelete = (taskId: number) => {
- apiClient(`projects/${projectId}/tasks/${taskId}`, {
+ apiClient(`projects/${project.id}/tasks/${taskId}`, {
method: "DELETE",
}).then((res) => {
- mutate();
+ tasksMutate();
});
};
diff --git a/client/src/providers/ProjectProvider.tsx b/client/src/providers/ProjectProvider.tsx
new file mode 100644
index 0000000..16c6916
--- /dev/null
+++ b/client/src/providers/ProjectProvider.tsx
@@ -0,0 +1,25 @@
+import { createContext } from "react";
+
+type ProjectProviderValue = {
+ project: {
+ id: number;
+ name: string;
+ };
+ projectMutate: Function;
+ tasksMutate: Function;
+};
+
+export const ProjectContext = createContext(null);
+
+type ProjectProviderProps = ProjectProviderValue & {
+ children: React.ReactNode;
+};
+
+export const ProjectProvider = ({
+ children,
+ ...rest
+}: ProjectProviderProps) => {
+ return (
+ {children}
+ );
+};