From 2b3ff2c461ff331a38467335bca79c6662b05b4e Mon Sep 17 00:00:00 2001 From: vboxuser Date: Sun, 2 Feb 2025 16:39:21 +0000 Subject: [PATCH] Add feature/fix: user administration enhancements --- .../ChatDemo/utils/userDialogInfo.tsx | 286 ++++++++++++++---- src/components/UserForm.tsx | 178 +++++++++++ src/context/UserContext.tsx | 57 ++++ src/pages/users.tsx | 43 ++- src/types.ts | 17 ++ 5 files changed, 519 insertions(+), 62 deletions(-) create mode 100644 src/components/UserForm.tsx diff --git a/src/components/ChatDemo/utils/userDialogInfo.tsx b/src/components/ChatDemo/utils/userDialogInfo.tsx index 19169d2..a77eeb6 100644 --- a/src/components/ChatDemo/utils/userDialogInfo.tsx +++ b/src/components/ChatDemo/utils/userDialogInfo.tsx @@ -1,8 +1,9 @@ -import { Loader, UserRound, ChevronDown, ChevronUp } from 'lucide-react'; +import { Loader, UserRound, ChevronDown, ChevronUp, Edit2, Save, X } from 'lucide-react'; import { User } from 'r2r-js'; import React, { useState, useEffect } from 'react'; import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/Button'; import { Card, CardHeader, CardContent } from '@/components/ui/card'; import CopyableContent from '@/components/ui/CopyableContent'; import { @@ -11,6 +12,22 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Switch } from '@/components/ui/switch'; +import { Label } from '@/components/ui/label'; +import { useToast } from '@/components/ui/use-toast'; import { useUserContext } from '@/context/UserContext'; interface UserInfoDialogProps { @@ -101,9 +118,20 @@ export const UserInfoDialog: React.FC = ({ open, onClose, }) => { - const { getClient } = useUserContext(); + const { getClient, deleteUser, updateUser } = useUserContext(); + const { toast } = useToast(); const [userProfile, setUserProfile] = useState(null); const [loading, setLoading] = useState(true); + const [isEditing, setIsEditing] = useState(false); + const [editedData, setEditedData] = useState<{ + name: string; + bio: string; + isSuperuser: boolean; + }>({ + name: '', + bio: '', + isSuperuser: false, + }); useEffect(() => { const fetchUser = async () => { @@ -120,6 +148,11 @@ export const UserInfoDialog: React.FC = ({ const user = await client.users.retrieve({ id }); setUserProfile(user.results); + setEditedData({ + name: user.results.name || '', + bio: user.results.bio || '', + isSuperuser: user.results.isSuperuser || false, + }); } catch (error) { console.error('Error fetching user:', error); } finally { @@ -130,11 +163,74 @@ export const UserInfoDialog: React.FC = ({ fetchUser(); }, [id, open, getClient]); + const handleDeleteUser = async () => { + try { + await deleteUser(id); + toast({ + title: "User Deleted", + description: "The user has been successfully deleted.", + }); + onClose(); + } catch (error) { + toast({ + title: "Error", + description: "Failed to delete user. Please try again.", + variant: "destructive", + }); + } + }; + + const handleSaveChanges = async () => { + try { + // Transform the editedData to match API's expected snake_case + const transformedData = { + name: editedData.name, + bio: editedData.bio, + is_superuser: editedData.isSuperuser + }; + + const updatedUser = await updateUser(id, transformedData); + setUserProfile(updatedUser); + setIsEditing(false); + toast({ + title: "Success", + description: "User information updated successfully.", + }); + } catch (error) { + toast({ + title: "Error", + description: "Failed to update user information.", + variant: "destructive", + }); + } + }; + return ( - User Details +
+ User Details + {!loading && userProfile && ( + + )} +
{loading ? ( @@ -150,67 +246,147 @@ export const UserInfoDialog: React.FC = ({
-
-

- {userProfile?.name || 'Unnamed User'} -

- {userProfile?.isSuperuser && ( - Admin - )} -
-

{userProfile?.email}

-

- -

+ {isEditing ? ( +
+
+ + + setEditedData({ ...editedData, name: e.target.value }) + } + placeholder="User name" + /> +
+
+ + setEditedData({ ...editedData, isSuperuser: checked }) + } + /> + +
+
+ ) : ( + <> +
+

+ {userProfile?.name || 'Unnamed User'} +

+ {userProfile?.isSuperuser && ( + Admin + )} +
+

{userProfile?.email}

+

+ +

+ + )}
- + {!isEditing && ( + <> + - + + + )} - {userProfile?.bio && ( -
-

- Bio -

-

{userProfile.bio}

+ {isEditing ? ( +
+
+ +