-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] Project Sidebar #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
98216b4
enhancement: add labels to project data
OverDsh 8997d5a
feat: create the project page sidebar
OverDsh 0a099ca
feat: create the project settings page
OverDsh 12a52d4
fix: fix useProject hook incorrectly handling the tanstack query cache
OverDsh 7cf022f
feat: redirect user on project delete
OverDsh 82dbd90
fix: typos in card's title/description
OverDsh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
42 changes: 42 additions & 0 deletions
42
src/app/dashboard/[teamSlug]/projects/[projectSlug]/components/ProjectSidebar.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <NavigationSidebar items={dynamicItems} isLoading={isTeamLoading || isProjectLoading} />; | ||
| } |
10 changes: 10 additions & 0 deletions
10
src/app/dashboard/[teamSlug]/projects/[projectSlug]/layout.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import ProjectSidebar from "./components/ProjectSidebar"; | ||
|
|
||
| export default function Layout({ children }: React.PropsWithChildren) { | ||
| return ( | ||
| <> | ||
| <ProjectSidebar /> | ||
| {children} | ||
| </> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectDangerZone.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| "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 { useRouter } from "next/navigation"; | ||
| import React from "react"; | ||
| import { toast } from "sonner"; | ||
|
|
||
| const CardComponent = ({ children }: React.PropsWithChildren) => { | ||
| return ( | ||
| <Card className="w-full"> | ||
| <CardHeader> | ||
| <CardTitle>DANGER ZONE</CardTitle> | ||
| </CardHeader> | ||
| <Separator /> | ||
| <CardContent> | ||
| <Item variant={"outline"} className="border-destructive bg-destructive/5"> | ||
| <ItemMedia variant={"icon"} className="border-none bg-destructive"> | ||
| <Trash className="stroke-destructive-foreground" /> | ||
| </ItemMedia> | ||
| <ItemContent> | ||
| <ItemTitle>Delete Project</ItemTitle> | ||
| <ItemDescription> | ||
| Your project will be permanently deleted including all of its data. This action is | ||
| irreversible. | ||
| </ItemDescription> | ||
| </ItemContent> | ||
| <ItemActions>{children}</ItemActions> | ||
| </Item> | ||
| </CardContent> | ||
| </Card> | ||
| ); | ||
| }; | ||
|
|
||
| export default function ProjectDangerZone() { | ||
| const router = useRouter(); | ||
| 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 }); | ||
| router.replace(`/dashboard/${team?.slug}`); | ||
| }) | ||
OverDsh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .catch((error) => { | ||
| if (error instanceof Error) { | ||
| toast.error(error.message, { id }); | ||
| } else { | ||
| toast.error("An unexpected error happened while deleting project", { | ||
| id, | ||
| }); | ||
| } | ||
| }); | ||
OverDsh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| if (isTeamLoading || isProjectLoading) { | ||
| return ( | ||
| <CardComponent> | ||
| <Skeleton className="h-9 w-30" /> | ||
| </CardComponent> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <CardComponent> | ||
| <CallbackDialog | ||
| title="Delete Project" | ||
| description="Are you sure you want to delete this project? This action is irreversible" | ||
| cancelButtonText="Cancel" | ||
| submitButtonText="Delete" | ||
| submitButtonVariant={"destructive"} | ||
| cancelButtonVariant={"outline"} | ||
| callback={handleProjectDeletion} | ||
| confirmationText={project?.name} | ||
| trigger={ | ||
| <Button variant={"destructive"} disabled={!hasPermission(team?.role, "DeleteProject")}> | ||
| Delete Project | ||
| </Button> | ||
| } | ||
| /> | ||
| </CardComponent> | ||
| ); | ||
| } | ||
125 changes: 125 additions & 0 deletions
125
src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/ProjectName.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) => ( | ||
| <Card className="w-full"> | ||
| <CardHeader> | ||
| <CardTitle>Project Name</CardTitle> | ||
| <CardDescription> | ||
| This is the name of your project displayed across the dashboard. | ||
| </CardDescription> | ||
| </CardHeader> | ||
| <Separator /> | ||
| <CardContent>{children}</CardContent> | ||
| </Card> | ||
| ); | ||
|
|
||
| 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 !== project?.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 ( | ||
| <CardComponent> | ||
| <div className="flex justify-between gap-2"> | ||
| <Skeleton className="h-9 w-full max-w-lg" /> | ||
| <Skeleton className="h-9 w-16" /> | ||
| </div> | ||
| </CardComponent> | ||
| ); | ||
| } | ||
|
|
||
| const disabled = !hasPermission(team?.role, "RenameProject"); | ||
|
|
||
| return ( | ||
| <CardComponent> | ||
| <Form {...form}> | ||
| <form | ||
| className="flex justify-between gap-2" | ||
| onSubmit={form.handleSubmit(() => setIsOpen(true))} | ||
| > | ||
| <FormField | ||
| control={form.control} | ||
| name="name" | ||
| render={({ field }) => ( | ||
| <FormItem className="w-full max-w-lg"> | ||
| <FormControl> | ||
| <Input placeholder={project?.name} disabled={disabled} {...field} /> | ||
| </FormControl> | ||
| <FormMessage /> | ||
| </FormItem> | ||
| )} | ||
| /> | ||
| <CallbackDialog | ||
| title="Rename Project" | ||
| description="Changing your project name will invalidate your current project URL. Are you sure you want to proceed?" | ||
| open={open} | ||
| onOpenChange={setIsOpen} | ||
| callback={() => { | ||
| form.handleSubmit(({ name }) => { | ||
| handleChangeName(name); | ||
| })(); | ||
| }} | ||
| /> | ||
| <Button disabled={disabled}>Save</Button> | ||
| </form> | ||
| </Form> | ||
| </CardComponent> | ||
| ); | ||
| } |
12 changes: 12 additions & 0 deletions
12
src/app/dashboard/[teamSlug]/projects/[projectSlug]/settings/page.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import ProjectDangerZone from "./ProjectDangerZone"; | ||
| import ProjectName from "./ProjectName"; | ||
|
|
||
| export default function Page() { | ||
| return ( | ||
| <> | ||
| <span className="w-full text-left text-lg font-semibold">Project Settings</span> | ||
| <ProjectName /> | ||
| <ProjectDangerZone /> | ||
| </> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.