Skip to content

Commit

Permalink
hook for update update workspace, new dialog for confirmation, functi…
Browse files Browse the repository at this point in the history
…ons of preferences working now
  • Loading branch information
Diivvuu committed Oct 2, 2024
1 parent 6b5e00c commit c9eb207
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 29 deletions.
66 changes: 66 additions & 0 deletions convex/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,69 @@ export const getById = query({
return await ctx.db.get(args.id);
},
});

export const update = mutation({
args: {
id: v.id("workspaces"),
name: v.string(),
},
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("Unauthorized");
}

const member = await ctx.db
.query("members")
.withIndex("by_workspace_id_user_id", (q) =>
q.eq("workspaceId", args.id).eq("userId", userId)
)
.unique();
if (!member || member.role !== "admin") {
throw new Error("Unauthorized");
}
await ctx.db.patch(args.id, {
name: args.name,
});

return args.id;
},
});

export const remove = mutation({
args: {
id: v.id("workspaces"),
},
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);

if (!userId) {
throw new Error("Unauthorized");
}

const member = await ctx.db
.query("members")
.withIndex("by_workspace_id_user_id", (q) =>
q.eq("workspaceId", args.id).eq("userId", userId)
)
.unique();
if (!member || member.role !== "admin") {
throw new Error("Unauthorized");
}

const [members] = await Promise.all([
ctx.db
.query("members")
.withIndex("by_workspace_id", (q) => q.eq("workspaceId", args.id))
.collect(),
]);

for (const member of members) {
await ctx.db.delete(member._id);
}

await ctx.db.delete(args.id);

return args.id;
},
});
135 changes: 110 additions & 25 deletions src/app/workspace/[workspaceId]/preferences-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,22 @@ import { useState } from "react";

import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { TrashIcon } from "lucide-react";
import { useUpdateWorkspace } from "@/features/workspaces/api/use-update-workspace";
import { useRemoveWorkspace } from "@/features/workspaces/api/use-remove-workspace";
import { DialogTrigger } from "@radix-ui/react-dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useWorkspaceId } from "@/hooks/use-workspace-id";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import { useConfirm } from "@/hooks/use-confirm";

interface PreferencesModalProps {
open: boolean;
Expand All @@ -18,33 +29,107 @@ export const PreferencesModal = ({
setOpen,
initialValue,
}: PreferencesModalProps) => {
const router = useRouter();
const [value, setValue] = useState(initialValue);
const [editOpen, setEditOpen] = useState(false);
const workspaceId = useWorkspaceId();
const { mutate: updateWorkspace, isPending: isUpdatingWorkspace } =
useUpdateWorkspace();
const { mutate: removeWorkspace, isPending: isRemovingWorkspace } =
useRemoveWorkspace();
const [ConfirmDialog, confirm] = useConfirm(
"Are you sure?",
"This action is irreversible"
);
const handleEdit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
updateWorkspace(
{ id: workspaceId, name: value },
{
onSuccess: () => {
toast.success("Workspace updated!");
setEditOpen(false);
},
onError: () => {
toast.error("Failed to update");
},
}
);
};

const handleRemove = async () => {
const ok = await confirm();
if (!ok) return;
removeWorkspace(
{ id: workspaceId },
{
onSuccess: () => {
toast.success("Workspace removed");
router.replace("/");
},
onError: () => {
toast.error("Failed to remove Workspace");
},
}
);
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="p-0 bg-gray-50 overflow-hidden">
<DialogHeader className="p-4 border-b bg-white">
<DialogTitle>{value}</DialogTitle>
</DialogHeader>
<div className="px-4 pb-4 flex flex-col gap-y-2">
<div className="px-5 py-4 bg-white rounded-lg border cursor-pointer hover:bg-gray-50">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold">Workspace name</p>
<p className="text-sm text-[#1264a3] hover:underline font-semibold">
Edit
</p>
</div>
<p className="text-sm">{value}</p>
<>
<ConfirmDialog />
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="p-0 bg-gray-50 overflow-hidden">
<DialogHeader className="p-4 border-b bg-white">
<DialogTitle>{value}</DialogTitle>
</DialogHeader>
<div className="px-4 pb-4 flex flex-col gap-y-2">
<Dialog open={editOpen} onOpenChange={setEditOpen}>
<DialogTrigger asChild>
<div className="px-5 py-4 bg-white rounded-lg border cursor-pointer hover:bg-gray-50">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold">Workspace name</p>
<p className="text-sm text-[#1264a3] hover:underline font-semibold">
Edit
</p>
</div>
<p className="text-sm">{value}</p>
</div>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename this worksapce</DialogTitle>
</DialogHeader>
<form className="space-y-4" onSubmit={handleEdit}>
<Input
value={value}
disabled={isUpdatingWorkspace}
onChange={(e) => setValue(e.target.value)}
required
minLength={3}
maxLength={80}
placeholder="Workspace name e.g. 'Work', 'Personal', 'Home' "
/>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline" disabled={isUpdatingWorkspace}>
Cancel
</Button>
</DialogClose>
<Button disabled={isUpdatingWorkspace}>Save</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<button
disabled={isRemovingWorkspace}
onClick={handleRemove}
className="flex items-center gap-x-2 px-5 py-4 bg-white rounded-lg border cursor-pointer hover:bg-gray-50 text-rose-600"
>
<TrashIcon className="size-4" />
<p className="text-sm font-semibold">Delete workspace</p>
</button>
</div>
<button
disabled={false}
onClick={() => {}}
className="flex items-center gap-x-2 px-5 py-4 bg-white rounded-lg border cursor-pointer hover:bg-gray-50 text-rose-600"
>
<TrashIcon className="size-4" />
<p className="text-sm font-semibold">Delete workspace</p>
</button>
</div>
</DialogContent>
</Dialog>
</DialogContent>
</Dialog>
</>
);
};
6 changes: 2 additions & 4 deletions src/features/workspaces/api/use-create-workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import { useMutation } from "convex/react";
import { api } from "../../../../convex/_generated/api";
import { useCallback, useMemo, useState } from "react";
import { Id } from "../../../../convex/_generated/dataModel";
import { stat } from "fs";

type RequestType = { name: string };
type ResponseType = Id<"workspaces"> | null;

type Options = {
onSuccess?: (data: ResponseType) => void;
onError?: (error: Error) => void;
Expand All @@ -18,8 +16,8 @@ type Options = {
export const useCreateWorkspace = () => {
const [data, setData] = useState<ResponseType>(null);
const [error, setError] = useState<Error | null>(null);
const [status, setStatus] = useState(
<"success" | "error" | "settled" | "pending" | null>null
const [status, setStatus] = useState
<"success" | "error" | "settled" | "pending" | null>(null
);

// const [isPending, setIsPending] = useState(false);
Expand Down
62 changes: 62 additions & 0 deletions src/features/workspaces/api/use-remove-workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useCallback, useMemo, useState } from "react";
import { Id } from "../../../../convex/_generated/dataModel";
import { useMutation } from "convex/react";
import { api } from "../../../../convex/_generated/api";

type RequestType = { id: Id<"workspaces"> };
type ResponseType = Id<"workspaces"> | null;

type Options = {
onSuccess?: (data: ResponseType) => void;
onError?: (error: Error) => void;
onSettled?: () => void;
throwError?: boolean;
};

export const useRemoveWorkspace = () => {
const [data, setData] = useState<ResponseType>(null);
const [error, setError] = useState<Error | null>(null);
const [status, setStatus] = useState<
"success" | "error" | "settled" | "pending" | null
>(null);
const isPending = useMemo(() => status === "pending", [status]);
const isSuccess = useMemo(() => status === "success", [status]);
const isError = useMemo(() => status === "error", [status]);
const isSettled = useMemo(() => status === "settled", [status]);

const mutation = useMutation(api.workspaces.remove);

const mutate = useCallback(
async (values: RequestType, options?: Options) => {
try {
setData(null);
setError(null);
setStatus("pending");

const response = await mutation(values);
options?.onSuccess?.(response);
setStatus("success");
return response;
} catch (error) {
options?.onError?.(error as Error);
setStatus("error");
if (options?.throwError) {
throw error;
}
} finally {
setStatus("settled");
options?.onSettled?.();
}
},
[mutation]
);
return {
mutate,
data,
error,
isPending,
isSuccess,
isError,
isSettled,
};
};
63 changes: 63 additions & 0 deletions src/features/workspaces/api/use-update-workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useMutation } from "convex/react";

import { api } from "../../../../convex/_generated/api";
import { useCallback, useMemo, useState } from "react";
import { Id } from "../../../../convex/_generated/dataModel";

type RequestType = { id: Id<"workspaces">; name: string };
type ResponseType = Id<"workspaces"> | null;
type Options = {
onSuccess?: (data: ResponseType) => void;
onError?: (error: Error) => void;
onSettled?: () => void;
throwError?: boolean;
};

export const useUpdateWorkspace = () => {
const [data, setData] = useState<ResponseType>(null);
const [error, setError] = useState<Error | null>(null);
const [status, setStatus] = useState<
"success" | "error" | "settled" | "pending" | null
>(null);

const isPending = useMemo(() => status === "pending", [status]);
const isSuccess = useMemo(() => status === "success", [status]);
const isError = useMemo(() => status === "error", [status]);
const isSettled = useMemo(() => status === "settled", [status]);

const mutation = useMutation(api.workspaces.update);

const mutate = useCallback(
async (values: RequestType, options?: Options) => {
try {
setData(null);
setError(null);
setStatus("pending");

const response = await mutation(values);
options?.onSuccess?.(response);
setStatus("success");
return response;
} catch (error) {
options?.onError?.(error as Error);
setStatus("error");
if (options?.throwError) {
throw error;
}
} finally {
setStatus("settled");
options?.onSettled?.();
}
},
[mutation]
);
return {
mutate,
data,
error,
isPending,
isSuccess,
isError,
isSettled,
};
};
Loading

0 comments on commit c9eb207

Please sign in to comment.