Skip to content

Commit

Permalink
Added rename board feature
Browse files Browse the repository at this point in the history
  • Loading branch information
jatin1510 committed May 24, 2024
1 parent 66afec9 commit 2341154
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 2 deletions.
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
import "./globals.css";
import { ConvexClientProvider } from "@/providers/convex-client-provider";
import { Toaster } from "@/components/ui/sonner";
import { ModalProvider } from "@/providers/modal-provider";
const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
Expand All @@ -20,6 +21,7 @@ export default function RootLayout({
<body className={inter.className}>
<ConvexClientProvider>
<Toaster />
<ModalProvider />
{children}
</ConvexClientProvider>
</body>
Expand Down
16 changes: 14 additions & 2 deletions components/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Link2, Trash2 } from "lucide-react";
import { Link2, Pencil, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { useApiMutation } from "@/hooks/use-api-mutation";
import { api } from "@/convex/_generated/api";
import { ConfirmModal } from "./confirm-modal";
import { Button } from "./ui/button";
import { useRenameModal } from "@/store/use-rename-modal";

interface ActionProps {
children: React.ReactNode;
Expand All @@ -31,6 +32,8 @@ export const Actions = ({
id,
title,
}: ActionProps) => {
const { onOpen } = useRenameModal();

const { mutate, pending } = useApiMutation(api.board.remove);

const onDelete = () => {
Expand Down Expand Up @@ -63,12 +66,21 @@ export const Actions = ({
<Link2 className="h-4 w-4 mr-2" />
Copy board link
</DropdownMenuItem>
<DropdownMenuItem
className="p-2 cursor-pointer"
onClick={() => onOpen(id, title)}
>
<Pencil className="h-4 w-4 mr-2" />
Rename
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmModal
header={`Delete board?`}
description={
<div>
To confirm, type <span className="font-semibold">{title}</span> in the box below
To confirm, type{" "}
<span className="font-semibold">{title}</span> in
the box below
</div>
}
disabled={pending}
Expand Down
70 changes: 70 additions & 0 deletions components/modals/rename-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use client";

import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
} from "@/components/ui/dialog";
import { api } from "@/convex/_generated/api";
import { useApiMutation } from "@/hooks/use-api-mutation";
import { useRenameModal } from "@/store/use-rename-modal";
import { FormEventHandler, useEffect, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

export const RenameModal = () => {
const { mutate, pending } = useApiMutation(api.board.update);

const { isOpen, onClose, initialValues } = useRenameModal();

const [title, setTitle] = useState(initialValues.title);

useEffect(() => {
setTitle(initialValues.title);
}, [initialValues.title]);

const onSubmit: FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();
mutate({ id: initialValues.id, title })
.then(() => {
toast.success("Board renamed");
})
.catch(() => toast.error("Failed to rename board"))
.finally(onClose);
};

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>Edit board title</DialogHeader>
<DialogDescription>
Enter a new title for this board
</DialogDescription>
<form onSubmit={onSubmit} className="space-y-4">
<Input
disabled={pending}
required
maxLength={60}
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Board title"
/>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
Cancel
</Button>
</DialogClose>
<Button disabled={pending} type="submit">
Save
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
};
21 changes: 21 additions & 0 deletions convex/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,24 @@ export const remove = mutation({
await ctx.db.delete(args.id);
},
});

export const update = mutation({
args: {
id: v.id("boards"),
title: v.string(),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthorized");
}

const title = args.title.trim();
if (!title) throw new Error("Title is required");

if (title.length > 60)
throw new Error("Title cannot be longer than 60 characters");

return await ctx.db.patch(args.id, { title: args.title });
},
});
22 changes: 22 additions & 0 deletions providers/modal-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client";

import { RenameModal } from "@/components/modals/rename-modal";
import { useEffect, useState } from "react";

export const ModalProvider = () => {
const [isMounted, setIsMounted] = useState(false);

useEffect(() => {
setIsMounted(true);
}, []);

if (!isMounted) {
return null;
}

return (
<>
<RenameModal />
</>
);
};
20 changes: 20 additions & 0 deletions store/use-rename-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { create } from "zustand";

const defaultValues = {
id: "",
title: "",
};

interface IRenameModal {
isOpen: boolean;
initialValues: typeof defaultValues;
onOpen: (id: string, title: string) => void;
onClose: () => void;
}

export const useRenameModal = create<IRenameModal>((set) => ({
isOpen: false,
onOpen: (id, title) => set({ isOpen: true, initialValues: { id, title } }),
onClose: () => set({ isOpen: false, initialValues: defaultValues }),
initialValues: defaultValues,
}));

0 comments on commit 2341154

Please sign in to comment.