From 98216b4ce43f963d167a8b2128b2d560c1906216 Mon Sep 17 00:00:00 2001 From: OverDsh Date: Sun, 5 Apr 2026 19:53:38 +0200 Subject: [PATCH 1/6] enhancement: add labels to project data --- src/app/dashboard/[teamSlug]/projects/[projectSlug]/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/page.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/page.tsx index ceb00d9..1bcd300 100644 --- a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/page.tsx +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/page.tsx @@ -8,8 +8,8 @@ export default function Page() { return (
-

{project?.name}

-

{project?.slug}

+

Name: {project?.name}

+

Slug: {project?.slug}

); } From 8997d5abdb93cbf8e8bdec867c9750fb634c3855 Mon Sep 17 00:00:00 2001 From: OverDsh Date: Sun, 5 Apr 2026 19:54:03 +0200 Subject: [PATCH 2/6] feat: create the project page sidebar --- .../components/ProjectSidebar.tsx | 42 +++++++++++++++++++ .../projects/[projectSlug]/layout.tsx | 10 +++++ 2 files changed, 52 insertions(+) create mode 100644 src/app/dashboard/[teamSlug]/projects/[projectSlug]/components/ProjectSidebar.tsx create mode 100644 src/app/dashboard/[teamSlug]/projects/[projectSlug]/layout.tsx diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/components/ProjectSidebar.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/components/ProjectSidebar.tsx new file mode 100644 index 0000000..7c76368 --- /dev/null +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/components/ProjectSidebar.tsx @@ -0,0 +1,42 @@ +"use client"; +import NavigationSidebar, { ItemGroup } from "@/src/components/NavigationSidebar"; +import { useProject } from "@/src/hooks/useProject"; +import { useTeam } from "@/src/hooks/useTeam"; +import { House, Settings } from "lucide-react"; + +export default function ProjectSidebar() { + const { data: team, isLoading: isTeamLoading } = useTeam(); + const { data: project, isLoading: isProjectLoading } = useProject(); + + const SidebarItems: ItemGroup[] = [ + { + groupTitle: "Workspace", + items: [ + { + title: "Overview", + Icon: House, + url: "", + }, + ], + }, + { + groupTitle: "Manage", + items: [ + { + title: "Project Settings", + Icon: Settings, + url: "/settings", + }, + ], + }, + ]; + + const dynamicItems = SidebarItems.map((group) => ({ + ...group, + items: group.items.map((item) => ({ + ...item, + url: `/dashboard/${team?.slug}/projects/${project?.slug}${item.url}`, + })), + })); + return ; +} diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/layout.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/layout.tsx new file mode 100644 index 0000000..2271133 --- /dev/null +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/layout.tsx @@ -0,0 +1,10 @@ +import ProjectSidebar from "./components/ProjectSidebar"; + +export default function Layout({ children }: React.PropsWithChildren) { + return ( + <> + + {children} + + ); +} From 0a099ca07c5cf0c3e09f997820f576d690fdb925 Mon Sep 17 00:00:00 2001 From: OverDsh Date: Sun, 5 Apr 2026 19:54:13 +0200 Subject: [PATCH 3/6] feat: create the project settings page --- .../settings/ProjectDangerZone.tsx | 99 ++++++++++++++ .../[projectSlug]/settings/ProjectName.tsx | 125 ++++++++++++++++++ .../projects/[projectSlug]/settings/page.tsx | 12 ++ 3 files changed, 236 insertions(+) create mode 100644 src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx create mode 100644 src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx create mode 100644 src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/page.tsx diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx new file mode 100644 index 0000000..482a6de --- /dev/null +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx @@ -0,0 +1,99 @@ +"use client"; +import CallbackDialog from "@/src/components/CallbackDialog"; +import { Button } from "@/src/components/ui/button"; +import { Card, CardHeader, CardTitle, CardContent } from "@/src/components/ui/card"; +import { + Item, + ItemActions, + ItemContent, + ItemDescription, + ItemMedia, + ItemTitle, +} from "@/src/components/ui/item"; +import { Separator } from "@/src/components/ui/separator"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { useProject, useProjectMutations } from "@/src/hooks/useProject"; +import { useTeam } from "@/src/hooks/useTeam"; +import { hasPermission } from "@/src/lib/utils/team-utils"; +import { Trash } from "lucide-react"; +import React from "react"; +import { toast } from "sonner"; + +const CardComponent = ({ children }: React.PropsWithChildren) => { + return ( + + + DANGER ZONE + + + + + + + + + Delete Team + + Your team will be permanently deleted including all of its projects. This action is + irreversible. + + + {children} + + + + ); +}; + +export default function ProjectDangerZone() { + const { data: team, isLoading: isTeamLoading } = useTeam(); + const { data: project, isLoading: isProjectLoading } = useProject(); + + const { deleteProject } = useProjectMutations(); + + const handleProjectDeletion = async () => { + const id = toast.loading("Deleting project..."); + deleteProject + .mutateAsync({ teamId: team!.id, projectId: project!.id }) + .then(() => { + toast.success("Successfully deleted project", { id }); + }) + .catch((error) => { + if (error instanceof Error) { + toast.error(error.message, { id }); + } else { + toast.error("An unexpected error happened while deleting project", { + id, + }); + } + }); + }; + + if (isTeamLoading || isProjectLoading) { + return ( + + + + ); + } + + return ( + + + Delete Project + + } + /> + + ); +} diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx new file mode 100644 index 0000000..736b63f --- /dev/null +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx @@ -0,0 +1,125 @@ +"use client"; +import CallbackDialog from "@/src/components/CallbackDialog"; +import { Button } from "@/src/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/src/components/ui/card"; +import { Form, FormControl, FormField, FormItem, FormMessage } from "@/src/components/ui/form"; +import { Input } from "@/src/components/ui/input"; +import { Separator } from "@/src/components/ui/separator"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { useProject, useProjectMutations } from "@/src/hooks/useProject"; +import { useTeam } from "@/src/hooks/useTeam"; +import { RenameProjectSchema } from "@/src/lib/types/project-types"; +import { hasPermission } from "@/src/lib/utils/team-utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; + +const CardComponent = ({ children }: React.PropsWithChildren) => ( + + + Project Name + + This is the name of your project displayed accross the dashboard. + + + + {children} + +); + +export default function ProjectName() { + const router = useRouter(); + const { data: team, isLoading: isTeamLoading } = useTeam(); + const { data: project, isLoading: isProjectLoading } = useProject(); + const [open, setIsOpen] = useState(false); + const { renameProject } = useProjectMutations(); + + const formSchema = RenameProjectSchema.refine((values) => values.name !== team?.name, { + error: "New project name must be different than your current project name", + }); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + }, + }); + + const handleChangeName = (name: string) => { + const id = toast.loading("Updating project name..."); + renameProject + .mutateAsync({ teamId: team!.id, projectId: project!.id, newName: name }) + .then(async (newProject) => { + toast.success("Successfully updated project name!", { + id, + }); + router.replace(`/dashboard/${team?.slug}/projects/${newProject?.slug}/settings`); + }) + .catch((error) => { + if (error instanceof Error) { + toast.error(error.message, { id }); + } else { + toast.error("An unexpected error happened while updating project name", { + id, + }); + } + }); + }; + + if (isTeamLoading || isProjectLoading) { + return ( + +
+ + +
+
+ ); + } + + const disabled = !hasPermission(team?.role, "RenameProject"); + + return ( + +
+ setIsOpen(true))} + > + ( + + + + + + + )} + /> + { + form.handleSubmit(({ name }) => { + handleChangeName(name); + })(); + }} + /> + + + +
+ ); +} diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/page.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/page.tsx new file mode 100644 index 0000000..5a02ebf --- /dev/null +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/page.tsx @@ -0,0 +1,12 @@ +import ProjectDangerZone from "./ProjectDangerZone"; +import ProjectName from "./ProjectName"; + +export default function Page() { + return ( + <> + Project Settings + + + + ); +} From 12a52d4b03bce39a7f3fa1bc08addce9b848ecef Mon Sep 17 00:00:00 2001 From: OverDsh Date: Sun, 5 Apr 2026 20:14:05 +0200 Subject: [PATCH 4/6] fix: fix useProject hook incorrectly handling the tanstack query cache --- src/hooks/useProject.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/hooks/useProject.ts b/src/hooks/useProject.ts index a5042b2..bb7ce7c 100644 --- a/src/hooks/useProject.ts +++ b/src/hooks/useProject.ts @@ -66,6 +66,11 @@ export function useProjectMutations() { return [...prevData, project]; }, ); + + queryClient.setQueryData( + ["project", variables.teamId, project.id], + () => project, + ); }, }); @@ -77,14 +82,18 @@ export function useProjectMutations() { teamId: string; projectId: string; }) => ProjectController.delete(teamId, projectId), - onSuccess: (project, variables) => { + onSuccess: (oldProject, variables) => { queryClient.setQueryData( ["projects", variables.teamId], (prevData) => { if (!prevData) return prevData; - return prevData.filter((p) => p.id !== project.id); + return prevData.filter((p) => p.id !== oldProject.id); }, ); + + queryClient.removeQueries({ + queryKey: ["project", variables.teamId, oldProject.slug], + }); }, }); @@ -106,6 +115,11 @@ export function useProjectMutations() { return prevData.map((p) => (p.id === project.id ? project : p)); }, ); + + queryClient.setQueryData( + ["project", variables.teamId, project.id], + () => project, + ); }, }); From 7cf022fa847d00d0c400848db2f62d02c3db8cd1 Mon Sep 17 00:00:00 2001 From: OverDsh Date: Sun, 5 Apr 2026 20:14:18 +0200 Subject: [PATCH 5/6] feat: redirect user on project delete --- .../projects/[projectSlug]/settings/ProjectDangerZone.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx index 482a6de..0d755b6 100644 --- a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx @@ -16,6 +16,7 @@ import { useProject, useProjectMutations } from "@/src/hooks/useProject"; import { useTeam } from "@/src/hooks/useTeam"; import { hasPermission } from "@/src/lib/utils/team-utils"; import { Trash } from "lucide-react"; +import { useRouter } from "next/navigation"; import React from "react"; import { toast } from "sonner"; @@ -46,6 +47,7 @@ const CardComponent = ({ children }: React.PropsWithChildren) => { }; export default function ProjectDangerZone() { + const router = useRouter(); const { data: team, isLoading: isTeamLoading } = useTeam(); const { data: project, isLoading: isProjectLoading } = useProject(); @@ -57,6 +59,7 @@ export default function ProjectDangerZone() { .mutateAsync({ teamId: team!.id, projectId: project!.id }) .then(() => { toast.success("Successfully deleted project", { id }); + router.replace(`/dashboard/${team?.slug}`); }) .catch((error) => { if (error instanceof Error) { From 82dbd90d5b530660d85027b5a934714d56c3f94e Mon Sep 17 00:00:00 2001 From: OverDsh Date: Sun, 5 Apr 2026 20:18:00 +0200 Subject: [PATCH 6/6] fix: typos in card's title/description --- .../projects/[projectSlug]/settings/ProjectDangerZone.tsx | 4 ++-- .../projects/[projectSlug]/settings/ProjectName.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx index 0d755b6..fbeaf85 100644 --- a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx @@ -33,9 +33,9 @@ const CardComponent = ({ children }: React.PropsWithChildren) => { - Delete Team + Delete Project - Your team will be permanently deleted including all of its projects. This action is + Your project will be permanently deleted including all of its data. This action is irreversible. diff --git a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx index 736b63f..6eaedc0 100644 --- a/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx +++ b/src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx @@ -27,7 +27,7 @@ const CardComponent = ({ children }: React.PropsWithChildren) => ( Project Name - This is the name of your project displayed accross the dashboard. + This is the name of your project displayed across the dashboard. @@ -42,7 +42,7 @@ export default function ProjectName() { const [open, setIsOpen] = useState(false); const { renameProject } = useProjectMutations(); - const formSchema = RenameProjectSchema.refine((values) => values.name !== team?.name, { + const formSchema = RenameProjectSchema.refine((values) => values.name !== project?.name, { error: "New project name must be different than your current project name", });