mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add user role editing functionality and update user role mutation
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,5 +40,6 @@ yarn-error.log*
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
|
.db
|
||||||
|
|
||||||
.db
|
.tool-versions
|
||||||
@@ -139,6 +139,7 @@ export const AddInvitation = () => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="member">Member</SelectItem>
|
<SelectItem value="member">Member</SelectItem>
|
||||||
|
<SelectItem value="admin">Admin</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|||||||
153
apps/dokploy/components/dashboard/settings/users/edit-role.tsx
Normal file
153
apps/dokploy/components/dashboard/settings/users/edit-role.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Edit3Icon } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const editUserRole = z.object({
|
||||||
|
role: z.enum(["member", "admin"]),
|
||||||
|
});
|
||||||
|
|
||||||
|
type EditUserRole = z.infer<typeof editUserRole>;
|
||||||
|
|
||||||
|
interface EditUserRoleProps {
|
||||||
|
userId: string;
|
||||||
|
currentRole: string;
|
||||||
|
userEmail: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditUserRole = ({ userId, currentRole, userEmail }: EditUserRoleProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const { mutateAsync: updateRoleMember, isLoading } = api.user.updateRoleMember.useMutation();
|
||||||
|
|
||||||
|
const form = useForm<EditUserRole>({
|
||||||
|
defaultValues: {
|
||||||
|
role: currentRole,
|
||||||
|
} as EditUserRole,
|
||||||
|
resolver: zodResolver(editUserRole),
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: EditUserRole) => {
|
||||||
|
if (data.role === currentRole) {
|
||||||
|
toast.info("No changes made");
|
||||||
|
setOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateRoleMember({
|
||||||
|
userId: userId,
|
||||||
|
role: data.role,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(`User role updated to ${data.role}`);
|
||||||
|
setOpen(false);
|
||||||
|
|
||||||
|
utils.user.all.invalidate();
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error?.message || "Error updating user role");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="w-full cursor-pointer"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<Edit3Icon className="h-4 w-4 mr-2" />
|
||||||
|
Edit Role
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit User Role</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Change the role for {userEmail}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
id="edit-user-role-form"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="role"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Role</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="member">Member</SelectItem>
|
||||||
|
<SelectItem value="admin">Admin</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
form="edit-user-role-form"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Update Role
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -31,6 +31,7 @@ import { MoreHorizontal, Users } from "lucide-react";
|
|||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { AddUserPermissions } from "./add-permissions";
|
import { AddUserPermissions } from "./add-permissions";
|
||||||
|
import { EditUserRole } from "./edit-role";
|
||||||
|
|
||||||
export const ShowUsers = () => {
|
export const ShowUsers = () => {
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
@@ -127,6 +128,14 @@ export const ShowUsers = () => {
|
|||||||
Actions
|
Actions
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
|
|
||||||
|
{member.role !== "owner" && (
|
||||||
|
<EditUserRole
|
||||||
|
userId={member.user.id}
|
||||||
|
currentRole={member.role}
|
||||||
|
userEmail={member.user.email}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{member.role !== "owner" && (
|
{member.role !== "owner" && (
|
||||||
<AddUserPermissions
|
<AddUserPermissions
|
||||||
userId={member.user.id}
|
userId={member.user.id}
|
||||||
|
|||||||
@@ -362,4 +362,58 @@ export const userRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return organizations.length;
|
return organizations.length;
|
||||||
}),
|
}),
|
||||||
|
updateRoleMember: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
userId: z.string(),
|
||||||
|
role: z.enum(["member", "admin"]),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const targetMember = await db.query.member.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(member.userId, input.userId),
|
||||||
|
eq(member.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetMember) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Member not found in this organization",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.user.id === input.userId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Cannot update your own role",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(member)
|
||||||
|
.set({
|
||||||
|
role: input.role,
|
||||||
|
})
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(member.userId, input.userId),
|
||||||
|
eq(member.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedMember = await db.query.member.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(member.userId, input.userId),
|
||||||
|
eq(member.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
),
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedMember;
|
||||||
|
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user