From 9a56e58bd54b2bfe11104e80c602493d720ff6be Mon Sep 17 00:00:00 2001
From: kamtschatka <simon.schatka@gmx.at>
Date: Sat, 19 Oct 2024 23:48:14 +0200
Subject: feature: Allow reseting user password, change their roles and create
 new users. Fixes #495  (#567)

* How do I set the variable "user" or "system" for AI inference #262
changed from system to user

* Make Myself an Admin #560
added  user management functionality to the admin page

* A bunch of UI fixes and simplifications

---------

Co-authored-by: Mohamed Bassem <me@mbassem.com>
---
 .../dashboard/admin/ChangeRoleDialog.tsx           | 154 +++++++++++++++++++++
 1 file changed, 154 insertions(+)
 create mode 100644 apps/web/components/dashboard/admin/ChangeRoleDialog.tsx

(limited to 'apps/web/components/dashboard/admin/ChangeRoleDialog.tsx')

diff --git a/apps/web/components/dashboard/admin/ChangeRoleDialog.tsx b/apps/web/components/dashboard/admin/ChangeRoleDialog.tsx
new file mode 100644
index 00000000..26ad5dce
--- /dev/null
+++ b/apps/web/components/dashboard/admin/ChangeRoleDialog.tsx
@@ -0,0 +1,154 @@
+import { useEffect, useState } from "react";
+import { ActionButton } from "@/components/ui/action-button";
+import { Button } from "@/components/ui/button";
+import {
+  Dialog,
+  DialogClose,
+  DialogContent,
+  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 { toast } from "@/components/ui/use-toast";
+import { api } from "@/lib/trpc";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { TRPCClientError } from "@trpc/client";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+
+import { changeRoleSchema } from "@hoarder/shared/types/admin";
+
+type ChangeRoleSchema = z.infer<typeof changeRoleSchema>;
+
+interface ChangeRoleDialogProps {
+  userId: string;
+  currentRole: "user" | "admin";
+  children?: React.ReactNode;
+}
+
+export default function ChangeRoleDialog({
+  userId,
+  currentRole,
+  children,
+}: ChangeRoleDialogProps) {
+  const apiUtils = api.useUtils();
+  const [isOpen, onOpenChange] = useState(false);
+  const form = useForm<ChangeRoleSchema>({
+    resolver: zodResolver(changeRoleSchema),
+    defaultValues: {
+      userId,
+      role: currentRole,
+    },
+  });
+  const { mutate, isPending } = api.admin.changeRole.useMutation({
+    onSuccess: () => {
+      toast({
+        description: "Role changed successfully",
+      });
+      apiUtils.users.list.invalidate();
+      onOpenChange(false);
+    },
+    onError: (error) => {
+      if (error instanceof TRPCClientError) {
+        toast({
+          variant: "destructive",
+          description: error.message,
+        });
+      } else {
+        toast({
+          variant: "destructive",
+          description: "Failed to change role",
+        });
+      }
+    },
+  });
+
+  useEffect(() => {
+    if (isOpen) {
+      form.reset();
+    }
+  }, [isOpen, form]);
+
+  return (
+    <Dialog open={isOpen} onOpenChange={onOpenChange}>
+      <DialogTrigger asChild>{children}</DialogTrigger>
+      <DialogContent>
+        <DialogTrigger asChild></DialogTrigger>
+        <DialogHeader>
+          <DialogTitle>Change Role</DialogTitle>
+        </DialogHeader>
+        <Form {...form}>
+          <form onSubmit={form.handleSubmit((val) => mutate(val))}>
+            <div className="flex w-full flex-col space-y-2">
+              <FormField
+                control={form.control}
+                name="role"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>Role</FormLabel>
+                    <FormControl>
+                      <Select
+                        onValueChange={field.onChange}
+                        defaultValue={field.value}
+                      >
+                        <SelectTrigger className="w-full">
+                          <SelectValue />
+                        </SelectTrigger>
+                        <SelectContent>
+                          <SelectItem value="user">User</SelectItem>
+                          <SelectItem value="admin">Admin</SelectItem>
+                        </SelectContent>
+                      </Select>
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+              <FormField
+                control={form.control}
+                name="userId"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormControl>
+                      <input type="hidden" {...field} />
+                    </FormControl>
+                  </FormItem>
+                )}
+              />
+              <DialogFooter className="sm:justify-end">
+                <DialogClose asChild>
+                  <Button type="button" variant="secondary">
+                    Close
+                  </Button>
+                </DialogClose>
+                <ActionButton
+                  type="submit"
+                  loading={isPending}
+                  disabled={isPending}
+                >
+                  Change
+                </ActionButton>
+              </DialogFooter>
+            </div>
+          </form>
+        </Form>
+      </DialogContent>
+    </Dialog>
+  );
+}
-- 
cgit v1.2.3-70-g09d2

