diff --git a/CHANGELOG.md b/CHANGELOG.md index 362489a..2d0e7a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Version 0.2.16 + +### Improved + +* move delete user button to user form +* disable deleting user on demo instance + ## Version 0.2.15 ### Improved diff --git a/components/UserForm.tsx b/components/UserForm.tsx index 9859dc0..d7af5ce 100644 --- a/components/UserForm.tsx +++ b/components/UserForm.tsx @@ -5,6 +5,17 @@ import { passwordSchema, usernameSchema } from '@/lib/zod'; import { useTranslations } from 'next-intl'; import { Input } from './ui/input'; import { Button } from './ui/button'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" import { Label } from './ui/label'; import { Switch } from './ui/switch'; import { Permission } from '@/lib/types'; @@ -58,6 +69,69 @@ export default function UserForm({ userId, onCancel, onSuccess }: UserFormProps) ); const isEditing = !!user; + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + + const handleDeleteUser = async () => { + if (!user) return; + + if (serverSettings.isDemo) { + toast({ + title: t('errorTitle'), + description: t('toastDemoDeleteDisabled'), + variant: 'destructive', + }); + return; + } + + if (currentUser && currentUser.id === user.id) { + toast({ + title: t('errorTitle'), + description: t('toastCannotDeleteSelf'), + variant: 'destructive', + }); + return; + } + + setIsDeleting(true); + try { + const response = await fetch('/api/user/delete', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userId: user.id }), + }); + + if (response.ok) { + setUsersData(prev => ({ + ...prev, + users: prev.users.filter(u => u.id !== user.id), + })); + toast({ + title: t('toastUserDeletedTitle'), + description: t('toastUserDeletedDescription', { username: user.username }), + variant: 'default' + }); + onSuccess(); + } else { + const errorData = await response.json(); + toast({ + title: t('errorTitle'), + description: errorData.error || t('genericError'), + variant: 'destructive', + }); + } + } catch (error) { + toast({ + title: t('errorTitle'), + description: t('networkError'), + variant: 'destructive', + }); + } finally { + setIsDeleting(false); + setShowDeleteConfirm(false); + } + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -94,11 +168,11 @@ export default function UserForm({ userId, onCancel, onSuccess }: UserFormProps) setUsersData(prev => ({ ...prev, users: prev.users.map(u => - u.id === user.id ? { - ...u, - username, - avatarPath, - permissions, + u.id === user.id ? { + ...u, + username, + avatarPath, + permissions, isAdmin, password: disablePassword ? '' : (password || u.password) // use the correct password to update atom } : u @@ -248,7 +322,7 @@ export default function UserForm({ userId, onCancel, onSuccess }: UserFormProps)

{t('demoPasswordDisabledMessage')}

)} - +
{error}

)} - + {currentUser && currentUser.isAdmin &&
+ {isEditing && ( + + + + + + + {t('areYouSure')} + + {t('deleteUserConfirmation', { username: user.username })} + + + + {t('cancel')} + + {isDeleting ? t('deletingButtonText') : t('confirmDeleteButtonText')} + + + + + )} )} - {showEdit && user.id !== currentLoggedInUserId && ( - - - - - - - {tWarning('areYouSure')} - - {t('deleteUserConfirmation', { username: user.username })} - - - - { e.stopPropagation(); setShowDeleteConfirm(false);}} disabled={isDeleting}>{tWarning('cancel')} - { e.stopPropagation(); handleDeleteUser();}} - disabled={isDeleting} - className="bg-destructive text-destructive-foreground hover:bg-destructive/90" - > - {isDeleting ? t('deletingButtonText') : t('confirmDeleteButtonText')} - - - - - )}
)}
@@ -190,14 +111,12 @@ function UserSelectionView({ onUserSelect, onEditUser, onCreateUser, - onUserDeleted, // Pass through the delete handler }: { users: User[], currentUserFromHook?: SafeUser, onUserSelect: (userId: string) => void, onEditUser: (userId: string) => void, onCreateUser: () => void, - onUserDeleted: (userId: string) => void, }) { return (
@@ -211,8 +130,6 @@ function UserSelectionView({ onEdit={() => onEditUser(user.id)} showEdit={!!currentUserFromHook?.isAdmin} isCurrentUser={false} // This card isn't the currently logged-in user for switching TO - currentLoggedInUserId={currentUserFromHook?.id} // For the "don't delete self" check - onUserDeleted={onUserDeleted} /> ))} {currentUserFromHook?.isAdmin && } @@ -230,12 +147,6 @@ export default function UserSelectModal({ onClose }: { onClose: () => void }) { const users = usersData.users; const { currentUser } = useHelpers(); - const handleUserDeleted = (userIdToDelete: string) => { - setUsersData(prevData => ({ - ...prevData, - users: prevData.users.filter(u => u.id !== userIdToDelete) - })); - }; const handleUserSelect = (userId: string) => { setSelectedUser(userId); @@ -281,7 +192,6 @@ export default function UserSelectModal({ onClose }: { onClose: () => void }) { onUserSelect={handleUserSelect} onEditUser={handleEditUser} onCreateUser={handleCreateUser} - onUserDeleted={handleUserDeleted} /> ) : isCreating || isEditing ? (