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')}
+
+
+
+
+ )}
@@ -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 ? (