diff --git a/.gitignore b/.gitignore
index 5e6e4eb3..21539388 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,5 +40,6 @@ yarn-error.log*
.DS_Store
*.pem
+.db
-.db
\ No newline at end of file
+.tool-versions
\ No newline at end of file
diff --git a/apps/dokploy/components/dashboard/settings/users/add-invitation.tsx b/apps/dokploy/components/dashboard/settings/users/add-invitation.tsx
index d05409fb..bfb05461 100644
--- a/apps/dokploy/components/dashboard/settings/users/add-invitation.tsx
+++ b/apps/dokploy/components/dashboard/settings/users/add-invitation.tsx
@@ -139,6 +139,7 @@ export const AddInvitation = () => {
Member
+ Admin
diff --git a/apps/dokploy/components/dashboard/settings/users/edit-role.tsx b/apps/dokploy/components/dashboard/settings/users/edit-role.tsx
new file mode 100644
index 00000000..db409723
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/users/edit-role.tsx
@@ -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;
+
+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({
+ 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 (
+
+ );
+};
\ No newline at end of file
diff --git a/apps/dokploy/components/dashboard/settings/users/show-users.tsx b/apps/dokploy/components/dashboard/settings/users/show-users.tsx
index 9580240d..01ce1a9a 100644
--- a/apps/dokploy/components/dashboard/settings/users/show-users.tsx
+++ b/apps/dokploy/components/dashboard/settings/users/show-users.tsx
@@ -31,6 +31,7 @@ import { MoreHorizontal, Users } from "lucide-react";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
import { AddUserPermissions } from "./add-permissions";
+import { EditUserRole } from "./edit-role";
export const ShowUsers = () => {
const { data: isCloud } = api.settings.isCloud.useQuery();
@@ -127,6 +128,14 @@ export const ShowUsers = () => {
Actions
+ {member.role !== "owner" && (
+
+ )}
+
{member.role !== "owner" && (
{
+ 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;
+
+ }),
});