Add user edit

This commit is contained in:
Chris Veleris 2025-10-15 16:48:03 +03:00
parent 3582a0ccd1
commit 7178f3dccb
27 changed files with 475 additions and 132 deletions

View file

@ -153,6 +153,78 @@ router.post('/admin/users', requireAdmin, async (req, res) => {
}
});
// PUT /api/admin/users/:id - update a user
router.put('/admin/users/:id', requireAdmin, async (req, res) => {
try {
const id = parseInt(req.params.id, 10);
if (!Number.isFinite(id))
return res.status(400).json({ error: 'Invalid user id' });
const user = await User.findByPk(id);
if (!user) return res.status(404).json({ error: 'User not found' });
const { email, password, name, surname, role } = req.body || {};
// Update email if provided
if (email !== undefined && email !== null) {
if (typeof email !== 'string' || !email.includes('@')) {
return res.status(400).json({ error: 'Invalid email' });
}
user.email = email;
}
// Update password if provided
if (password && password.trim() !== '') {
if (typeof password !== 'string' || password.length < 6) {
return res
.status(400)
.json({ error: 'Password must be at least 6 characters' });
}
user.password = password;
}
// Update name and surname - handle empty strings properly
if (name !== undefined) user.name = name || null;
if (surname !== undefined) user.surname = surname || null;
await user.save();
// Update role if provided
if (role !== undefined) {
const makeAdmin = role === 'admin';
const [userRole] = await Role.findOrCreate({
where: { user_id: user.id },
defaults: { user_id: user.id, is_admin: makeAdmin },
});
if (userRole.is_admin !== makeAdmin) {
userRole.is_admin = makeAdmin;
await userRole.save();
}
}
// Fetch updated role
const userRole = await Role.findOne({ where: { user_id: user.id } });
res.json({
id: user.id,
email: user.email,
name: user.name,
surname: user.surname,
created_at: user.created_at,
role: userRole?.is_admin ? 'admin' : 'user',
});
} catch (err) {
logError('Error updating user:', err);
// Unique constraint
if (err?.name === 'SequelizeUniqueConstraintError') {
return res.status(409).json({ error: 'Email already exists' });
}
res.status(400).json({
error: 'There was a problem updating the user.',
});
}
});
// DELETE /api/admin/users/:id - delete a user, prevent self-delete
router.delete('/admin/users/:id', requireAdmin, async (req, res) => {
try {

View file

@ -1,7 +1,13 @@
import React, { useEffect, useMemo, useState, useRef } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ChevronDownIcon, CheckIcon } from '@heroicons/react/24/outline';
import {
ChevronDownIcon,
CheckIcon,
PencilIcon,
TrashIcon,
} from '@heroicons/react/24/outline';
import ConfirmDialog from '../Shared/ConfirmDialog';
interface AdminUserItem {
id: number;
@ -64,6 +70,49 @@ const createAdminUser = async (
return await res.json();
};
const updateAdminUser = async (
id: number,
email: string,
t: any,
name?: string,
surname?: string,
role?: 'admin' | 'user',
password?: string
): Promise<AdminUserItem> => {
const body: any = { email, name, surname, role };
if (password) body.password = password;
const res = await fetch(`/api/admin/users/${id}`, {
method: 'PUT',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(body),
});
if (res.status === 401)
throw new Error(
t('admin.authenticationRequired', 'Authentication required')
);
if (res.status === 403) throw new Error(t('admin.forbidden', 'Forbidden'));
if (res.status === 409)
throw new Error(t('admin.emailAlreadyExists', 'Email already exists'));
if (res.status === 404)
throw new Error(t('admin.userNotFound', 'User not found'));
if (!res.ok) {
let message = t('admin.failedToUpdateUser', 'Failed to update user');
try {
const body = await res.json();
if (body?.error) message = body.error;
} catch {
// ignore non-JSON error bodies
}
throw new Error(message);
}
return await res.json();
};
const deleteAdminUser = async (id: number, t: any): Promise<void> => {
const res = await fetch(`/api/admin/users/${id}`, {
method: 'DELETE',
@ -91,7 +140,9 @@ const AddUserModal: React.FC<{
isOpen: boolean;
onClose: () => void;
onCreated: (user: AdminUserItem) => void;
}> = ({ isOpen, onClose, onCreated }) => {
onUpdated: (user: AdminUserItem) => void;
editingUser?: AdminUserItem | null;
}> = ({ isOpen, onClose, onCreated, onUpdated, editingUser }) => {
const { t } = useTranslation();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
@ -110,15 +161,23 @@ const AddUserModal: React.FC<{
useEffect(() => {
if (isOpen) {
setEmail('');
setPassword('');
setName('');
setSurname('');
setRole('user');
if (editingUser) {
setEmail(editingUser.email);
setPassword('');
setName(editingUser.name || '');
setSurname(editingUser.surname || '');
setRole(editingUser.role);
} else {
setEmail('');
setPassword('');
setName('');
setSurname('');
setRole('user');
}
setError(null);
setIsRoleDropdownOpen(false);
}
}, [isOpen]);
}, [isOpen, editingUser]);
// Close dropdown when clicking outside
useEffect(() => {
@ -144,7 +203,7 @@ const AddUserModal: React.FC<{
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
if (!email || !password) {
if (!email) {
setError(t('errors.required', 'This field is required'));
return;
}
@ -152,22 +211,45 @@ const AddUserModal: React.FC<{
setError(t('errors.invalidEmail', 'Invalid email address'));
return;
}
// Password is required for new users, optional for updates
if (!editingUser && !password) {
setError(t('errors.required', 'This field is required'));
return;
}
setSubmitting(true);
try {
const user = await createAdminUser(
email,
password,
t,
name,
surname,
role
);
onCreated(user);
if (editingUser) {
const user = await updateAdminUser(
editingUser.id,
email,
t,
name,
surname,
role,
password || undefined
);
onUpdated(user);
} else {
const user = await createAdminUser(
email,
password,
t,
name,
surname,
role
);
onCreated(user);
}
onClose();
} catch (err: any) {
setError(
err.message ||
t('admin.failedToCreateUser', 'Failed to create user')
(editingUser
? t('admin.failedToUpdateUser', 'Failed to update user')
: t(
'admin.failedToCreateUser',
'Failed to create user'
))
);
} finally {
setSubmitting(false);
@ -184,7 +266,9 @@ const AddUserModal: React.FC<{
onClick={(e) => e.stopPropagation()}
>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
{t('admin.addUser', 'Add user')}
{editingUser
? t('admin.editUser', 'Edit user')
: t('admin.addUser', 'Add user')}
</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
@ -224,13 +308,23 @@ const AddUserModal: React.FC<{
<div>
<label className="block text-sm text-gray-700 dark:text-gray-300 mb-1">
{t('admin.password', 'Password')}
{editingUser && (
<span className="text-xs text-gray-500 dark:text-gray-400 ml-2">
(
{t(
'admin.passwordOptional',
'Leave blank to keep current'
)}
)
</span>
)}
</label>
<input
type="password"
className="w-full rounded border px-3 py-2 bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
required={!editingUser}
minLength={6}
/>
</div>
@ -321,7 +415,9 @@ const AddUserModal: React.FC<{
>
{submitting
? t('common.saving', 'Saving...')
: t('common.create', 'Create')}
: editingUser
? t('common.save', 'Save')
: t('common.create', 'Create')}
</button>
</div>
</form>
@ -335,12 +431,13 @@ const AdminUsersPage: React.FC = () => {
const [users, setUsers] = useState<AdminUserItem[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
const [addOpen, setAddOpen] = useState(false);
const [editingUser, setEditingUser] = useState<AdminUserItem | null>(null);
const [userToDelete, setUserToDelete] = useState<AdminUserItem | null>(
null
);
const navigate = useNavigate();
const selectedCount = selectedIds.size;
const load = async () => {
setLoading(true);
setError(null);
@ -363,57 +460,24 @@ const AdminUsersPage: React.FC = () => {
load();
}, []);
const toggleSelect = (id: number) => {
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
};
const handleDeleteUser = async () => {
if (!userToDelete) return;
const toggleSelectAll = () => {
if (!users) return;
setSelectedIds((prev) => {
if (prev.size === users.length) return new Set();
return new Set(users.map((u) => u.id));
});
};
const removeSelected = async () => {
if (!users || selectedIds.size === 0) return;
const toDelete = Array.from(selectedIds);
const remaining: AdminUserItem[] = [];
const byId = new Map(users.map((u) => [u.id, u] as const));
for (const id of toDelete) {
try {
await deleteAdminUser(id, t);
} catch (err: any) {
// Keep the user if deletion failed and surface error inline later
console.error(
t('admin.failedToDeleteUser', 'Failed to delete user'),
id,
err?.message
);
remaining.push(byId.get(id)!);
}
try {
await deleteAdminUser(userToDelete.id, t);
setUsers((prev) =>
prev ? prev.filter((u) => u.id !== userToDelete.id) : null
);
setUserToDelete(null);
} catch (err: any) {
setError(
err.message ||
t('admin.failedToDeleteUser', 'Failed to delete user')
);
setUserToDelete(null);
}
const next = users.filter((u) => !toDelete.includes(u.id));
// If any failed, keep them
const nextWithFailures = remaining.length
? next.concat(
remaining.filter((r) => !next.find((n) => n.id === r.id))
)
: next;
setUsers(nextWithFailures);
setSelectedIds(new Set());
};
const headerCheckboxChecked = useMemo(() => {
if (!users || users.length === 0) return false;
return selectedIds.size === users.length;
}, [users, selectedIds]);
return (
<div className="flex justify-center px-4 lg:px-2">
<div className="w-full max-w-5xl space-y-6">
@ -421,21 +485,15 @@ const AdminUsersPage: React.FC = () => {
<h2 className="text-2xl font-light">
{t('admin.userManagement', 'User Management')}
</h2>
<div className="flex items-center space-x-2">
<button
onClick={() => setAddOpen(true)}
className="px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 focus:outline-none transition duration-150 ease-in-out text-sm"
>
{t('admin.addUser', 'Add user')}
</button>
<button
onClick={removeSelected}
disabled={selectedCount === 0}
className="px-4 py-2 rounded-md border border-red-300 dark:border-red-600 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 focus:outline-none transition duration-150 ease-in-out text-sm disabled:opacity-50 disabled:cursor-not-allowed"
>
{t('admin.remove', 'Remove')}
</button>
</div>
<button
onClick={() => {
setEditingUser(null);
setAddOpen(true);
}}
className="px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 focus:outline-none transition duration-150 ease-in-out text-sm"
>
{t('admin.addUser', 'Add user')}
</button>
</div>
{error && (
@ -448,14 +506,6 @@ const AdminUsersPage: React.FC = () => {
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-900">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<input
type="checkbox"
checked={headerCheckboxChecked}
onChange={toggleSelectAll}
className="w-4 h-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2 bg-white dark:bg-gray-700"
/>
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
{t('admin.email', 'Email')}
</th>
@ -471,6 +521,9 @@ const AdminUsersPage: React.FC = () => {
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
{t('admin.role', 'Role')}
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
{t('admin.actions', 'Actions')}
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@ -504,16 +557,6 @@ const AdminUsersPage: React.FC = () => {
key={u.id}
className="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors duration-150"
>
<td className="px-6 py-4 whitespace-nowrap">
<input
type="checkbox"
checked={selectedIds.has(u.id)}
onChange={() =>
toggleSelect(u.id)
}
className="w-4 h-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2 bg-white dark:bg-gray-700"
/>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
{u.email}
</td>
@ -537,6 +580,35 @@ const AdminUsersPage: React.FC = () => {
: t('admin.user', 'user')}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => {
setEditingUser(u);
setAddOpen(true);
}}
className="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
title={t(
'common.edit',
'Edit'
)}
>
<PencilIcon className="h-5 w-5" />
</button>
<button
onClick={() =>
setUserToDelete(u)
}
className="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
title={t(
'common.delete',
'Delete'
)}
>
<TrashIcon className="h-5 w-5" />
</button>
</div>
</td>
</tr>
))}
</tbody>
@ -545,11 +617,35 @@ const AdminUsersPage: React.FC = () => {
<AddUserModal
isOpen={addOpen}
onClose={() => setAddOpen(false)}
onClose={() => {
setAddOpen(false);
setEditingUser(null);
}}
onCreated={(user) =>
setUsers((prev) => (prev ? [user, ...prev] : [user]))
}
onUpdated={(user) =>
setUsers((prev) =>
prev
? prev.map((u) => (u.id === user.id ? user : u))
: [user]
)
}
editingUser={editingUser}
/>
{userToDelete && (
<ConfirmDialog
title={t('admin.deleteUser', 'Delete User')}
message={t(
'admin.confirmDeleteUser',
'Are you sure you want to delete {{email}}? This action cannot be undone.',
{ email: userToDelete.email }
)}
onConfirm={handleDeleteUser}
onCancel={() => setUserToDelete(null)}
/>
)}
</div>
</div>
);

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "فشل في إنشاء المستخدم",
"badRequest": "طلب غير صحيح",
"userNotFound": "المستخدم غير موجود",
"failedToDeleteUser": "فشل في حذف المستخدم"
"failedToDeleteUser": "فشل في حذف المستخدم",
"editUser": "تعديل المستخدم",
"actions": "الإجراءات",
"passwordOptional": "اتركه فارغًا للاحتفاظ بالحالي",
"failedToUpdateUser": "فشل في تحديث المستخدم",
"confirmDelete": "هل أنت متأكد أنك تريد حذف هذا المستخدم؟",
"deleteUser": "حذف المستخدم",
"confirmDeleteUser": "هل أنت متأكد أنك تريد حذف {{email}}؟ لا يمكن التراجع عن هذا الإجراء."
},
"shares": {
"shareProject": "مشاركة المشروع",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Неуспешно създаване на потребител",
"badRequest": "Невалидна заявка",
"userNotFound": "Потребителят не е намерен",
"failedToDeleteUser": "Неуспешно изтриване на потребител"
"failedToDeleteUser": "Неуспешно изтриване на потребител",
"editUser": "Редактиране на потребител",
"actions": "Действия",
"passwordOptional": "Оставете празно, за да запазите текущата",
"failedToUpdateUser": "Неуспешно обновяване на потребителя",
"confirmDelete": "Сигурни ли сте, че искате да изтриете този потребител?",
"deleteUser": "Изтриване на потребител",
"confirmDeleteUser": "Сигурни ли сте, че искате да изтриете {{email}}? Това действие не може да бъде отменено."
},
"shares": {
"shareProject": "Сподели проект",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Kunne ikke oprette bruger",
"badRequest": "Ugyldig anmodning",
"userNotFound": "Bruger ikke fundet",
"failedToDeleteUser": "Kunne ikke slette bruger"
"failedToDeleteUser": "Kunne ikke slette bruger",
"editUser": "Rediger bruger",
"actions": "Handlinger",
"passwordOptional": "Lad være tom for at bevare nuværende",
"failedToUpdateUser": "Kunne ikke opdatere bruger",
"confirmDelete": "Er du sikker på, at du vil slette denne bruger?",
"deleteUser": "Slet bruger",
"confirmDeleteUser": "Er du sikker på, at du vil slette {{email}}? Denne handling kan ikke fortrydes."
},
"shares": {
"shareProject": "Del projekt",

View file

@ -963,7 +963,14 @@
"failedToCreateUser": "Benutzer konnte nicht erstellt werden",
"badRequest": "Ungültige Anfrage",
"userNotFound": "Benutzer nicht gefunden",
"failedToDeleteUser": "Benutzer konnte nicht gelöscht werden"
"failedToDeleteUser": "Benutzer konnte nicht gelöscht werden",
"editUser": "Benutzer bearbeiten",
"actions": "Aktionen",
"passwordOptional": "Feld leer lassen, um das aktuelle Passwort beizubehalten",
"failedToUpdateUser": "Fehler beim Aktualisieren des Benutzers",
"confirmDelete": "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?",
"deleteUser": "Benutzer löschen",
"confirmDeleteUser": "Sind Sie sicher, dass Sie {{email}} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."
},
"shares": {
"shareProject": "Projekt teilen",

View file

@ -940,15 +940,18 @@
"manageUsers": "Διαχείριση χρηστών",
"userManagement": "Διαχείριση Χρηστών",
"addUser": "Προσθήκη χρήστη",
"editUser": "Επεξεργασία χρήστη",
"remove": "Αφαίρεση",
"email": "Ηλεκτρονικό ταχυδρομείο",
"created": "Δημιουργήθηκε",
"role": "Ρόλος",
"actions": "Ενέργειες",
"loadingUsers": "Φόρτωση χρηστών...",
"noUsers": "Δεν υπάρχουν χρήστες",
"admin": "διαχειριστής",
"user": "χρήστης",
"password": "Κωδικός πρόσβασης",
"passwordOptional": "Αφήστε το κενό για να διατηρήσετε τον τρέχοντα",
"name": "Όνομα",
"surname": "Επώνυμο",
"authenticationRequired": "Απαιτείται αυθεντικοποίηση",
@ -956,9 +959,13 @@
"failedToLoadUsers": "Αποτυχία φόρτωσης χρηστών",
"emailAlreadyExists": "Το email υπάρχει ήδη",
"failedToCreateUser": "Αποτυχία δημιουργίας χρήστη",
"failedToUpdateUser": "Αποτυχία ενημέρωσης χρήστη",
"badRequest": "Κακή αίτηση",
"userNotFound": "Ο χρήστης δεν βρέθηκε",
"failedToDeleteUser": "Αποτυχία διαγραφής χρήστη"
"failedToDeleteUser": "Αποτυχία διαγραφής χρήστη",
"confirmDelete": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον χρήστη;",
"deleteUser": "Διαγραφή Χρήστη",
"confirmDeleteUser": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το {{email}}; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί."
},
"shares": {
"shareProject": "Κοινή χρήση έργου",

View file

@ -937,25 +937,32 @@
"manageUsers": "Manage users",
"userManagement": "User Management",
"addUser": "Add user",
"editUser": "Edit user",
"remove": "Remove",
"email": "Email",
"name": "Name",
"surname": "Surname",
"created": "Created",
"role": "Role",
"actions": "Actions",
"loadingUsers": "Loading users...",
"noUsers": "No users",
"admin": "admin",
"user": "user",
"password": "Password",
"passwordOptional": "Leave blank to keep current",
"authenticationRequired": "Authentication required",
"forbidden": "Forbidden",
"failedToLoadUsers": "Failed to load users",
"emailAlreadyExists": "Email already exists",
"failedToCreateUser": "Failed to create user",
"failedToUpdateUser": "Failed to update user",
"badRequest": "Bad request",
"userNotFound": "User not found",
"failedToDeleteUser": "Failed to delete user"
"failedToDeleteUser": "Failed to delete user",
"confirmDelete": "Are you sure you want to delete this user?",
"deleteUser": "Delete User",
"confirmDeleteUser": "Are you sure you want to delete {{email}}? This action cannot be undone."
},
"shares": {
"shareProject": "Share project",

View file

@ -955,7 +955,14 @@
"failedToCreateUser": "Error al crear usuario",
"badRequest": "Solicitud incorrecta",
"userNotFound": "Usuario no encontrado",
"failedToDeleteUser": "Error al eliminar usuario"
"failedToDeleteUser": "Error al eliminar usuario",
"editUser": "Editar usuario",
"actions": "Acciones",
"passwordOptional": "Dejar en blanco para mantener el actual",
"failedToUpdateUser": "Error al actualizar el usuario",
"confirmDelete": "¿Está seguro de que desea eliminar este usuario?",
"deleteUser": "Eliminar usuario",
"confirmDeleteUser": "¿Está seguro de que desea eliminar {{email}}? Esta acción no se puede deshacer."
},
"shares": {
"shareProject": "Compartir proyecto",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Käyttäjän luominen epäonnistui",
"badRequest": "Virheellinen pyyntö",
"userNotFound": "Käyttäjää ei löytynyt",
"failedToDeleteUser": "Käyttäjän poistaminen epäonnistui"
"failedToDeleteUser": "Käyttäjän poistaminen epäonnistui",
"editUser": "Muokkaa käyttäjää",
"actions": "Toiminnot",
"passwordOptional": "Jätä tyhjäksi säilyttääksesi nykyisen",
"failedToUpdateUser": "Käyttäjän päivittäminen epäonnistui",
"confirmDelete": "Oletko varma, että haluat poistaa tämän käyttäjän?",
"deleteUser": "Poista käyttäjä",
"confirmDeleteUser": "Oletko varma, että haluat poistaa {{email}}? Tätä toimintoa ei voi peruuttaa."
},
"shares": {
"shareProject": "Jaa projekti",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Échec de la création de l'utilisateur",
"badRequest": "Mauvaise requête",
"userNotFound": "Utilisateur non trouvé",
"failedToDeleteUser": "Échec de la suppression de l'utilisateur"
"failedToDeleteUser": "Échec de la suppression de l'utilisateur",
"editUser": "Modifier l'utilisateur",
"actions": "Actions",
"passwordOptional": "Laissez vide pour conserver l'actuel",
"failedToUpdateUser": "Échec de la mise à jour de l'utilisateur",
"confirmDelete": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?",
"deleteUser": "Supprimer l'utilisateur",
"confirmDeleteUser": "Êtes-vous sûr de vouloir supprimer {{email}} ? Cette action ne peut pas être annulée."
},
"shares": {
"shareProject": "Partager le projet",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Gagal membuat pengguna",
"badRequest": "Permintaan tidak valid",
"userNotFound": "Pengguna tidak ditemukan",
"failedToDeleteUser": "Gagal menghapus pengguna"
"failedToDeleteUser": "Gagal menghapus pengguna",
"editUser": "Edit pengguna",
"actions": "Tindakan",
"passwordOptional": "Biarkan kosong untuk mempertahankan yang sekarang",
"failedToUpdateUser": "Gagal memperbarui pengguna",
"confirmDelete": "Apakah Anda yakin ingin menghapus pengguna ini?",
"deleteUser": "Hapus Pengguna",
"confirmDeleteUser": "Apakah Anda yakin ingin menghapus {{email}}? Tindakan ini tidak dapat dibatalkan."
},
"shares": {
"shareProject": "Bagikan proyek",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Impossibile creare l'utente",
"badRequest": "Richiesta non valida",
"userNotFound": "Utente non trovato",
"failedToDeleteUser": "Impossibile eliminare l'utente"
"failedToDeleteUser": "Impossibile eliminare l'utente",
"editUser": "Modifica utente",
"actions": "Azioni",
"passwordOptional": "Lascia vuoto per mantenere quello attuale",
"failedToUpdateUser": "Impossibile aggiornare l'utente",
"confirmDelete": "Sei sicuro di voler eliminare questo utente?",
"deleteUser": "Elimina utente",
"confirmDeleteUser": "Sei sicuro di voler eliminare {{email}}? Questa azione non può essere annullata."
},
"shares": {
"shareProject": "Condividi progetto",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "ユーザーの作成に失敗しました",
"badRequest": "不正なリクエスト",
"userNotFound": "ユーザーが見つかりません",
"failedToDeleteUser": "ユーザーの削除に失敗しました"
"failedToDeleteUser": "ユーザーの削除に失敗しました",
"editUser": "ユーザーを編集",
"actions": "アクション",
"passwordOptional": "現在のままにするには空白のままにしてください",
"failedToUpdateUser": "ユーザーの更新に失敗しました",
"confirmDelete": "このユーザーを削除してもよろしいですか?",
"deleteUser": "ユーザーを削除",
"confirmDeleteUser": "{{email}} を削除してもよろしいですか?この操作は元に戻せません。"
},
"shares": {
"shareProject": "プロジェクトを共有",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "사용자 생성 실패",
"badRequest": "잘못된 요청",
"userNotFound": "사용자를 찾을 수 없습니다",
"failedToDeleteUser": "사용자 삭제 실패"
"failedToDeleteUser": "사용자 삭제 실패",
"editUser": "사용자 편집",
"actions": "작업",
"passwordOptional": "현재 비밀번호를 유지하려면 비워 두세요",
"failedToUpdateUser": "사용자 업데이트에 실패했습니다",
"confirmDelete": "이 사용자를 삭제하시겠습니까?",
"deleteUser": "사용자 삭제",
"confirmDeleteUser": "{{email}}을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다."
},
"shares": {
"shareProject": "프로젝트 공유",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Kon gebruiker niet aanmaken",
"badRequest": "Ongeldig verzoek",
"userNotFound": "Gebruiker niet gevonden",
"failedToDeleteUser": "Kon gebruiker niet verwijderen"
"failedToDeleteUser": "Kon gebruiker niet verwijderen",
"editUser": "Bewerk gebruiker",
"actions": "Acties",
"passwordOptional": "Laat leeg om huidige te behouden",
"failedToUpdateUser": "Bijwerken van gebruiker mislukt",
"confirmDelete": "Weet u zeker dat u deze gebruiker wilt verwijderen?",
"deleteUser": "Verwijder gebruiker",
"confirmDeleteUser": "Weet u zeker dat u {{email}} wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt."
},
"shares": {
"shareProject": "Project delen",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Kunne ikke opprette bruker",
"badRequest": "Feil forespørsel",
"userNotFound": "Bruker ikke funnet",
"failedToDeleteUser": "Kunne ikke slette bruker"
"failedToDeleteUser": "Kunne ikke slette bruker",
"editUser": "Rediger bruker",
"actions": "Handlinger",
"passwordOptional": "La stå blankt for å beholde nåværende",
"failedToUpdateUser": "Kunne ikke oppdatere bruker",
"confirmDelete": "Er du sikker på at du vil slette denne brukeren?",
"deleteUser": "Slett bruker",
"confirmDeleteUser": "Er du sikker på at du vil slette {{email}}? Denne handlingen kan ikke angres."
},
"shares": {
"shareProject": "Del prosjekt",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Nie udało się utworzyć użytkownika",
"badRequest": "Złe żądanie",
"userNotFound": "Użytkownik nie znaleziony",
"failedToDeleteUser": "Nie udało się usunąć użytkownika"
"failedToDeleteUser": "Nie udało się usunąć użytkownika",
"editUser": "Edytuj użytkownika",
"actions": "Akcje",
"passwordOptional": "Pozostaw puste, aby zachować obecne",
"failedToUpdateUser": "Nie udało się zaktualizować użytkownika",
"confirmDelete": "Czy na pewno chcesz usunąć tego użytkownika?",
"deleteUser": "Usuń użytkownika",
"confirmDeleteUser": "Czy na pewno chcesz usunąć {{email}}? Ta akcja nie może być cofnięta."
},
"shares": {
"shareProject": "Udostępnij projekt",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Falha ao criar usuário",
"badRequest": "Requisição inválida",
"userNotFound": "Usuário não encontrado",
"failedToDeleteUser": "Falha ao deletar usuário"
"failedToDeleteUser": "Falha ao deletar usuário",
"editUser": "Editar usuário",
"actions": "Ações",
"passwordOptional": "Deixe em branco para manter o atual",
"failedToUpdateUser": "Falha ao atualizar o usuário",
"confirmDelete": "Você tem certeza de que deseja excluir este usuário?",
"deleteUser": "Excluir Usuário",
"confirmDeleteUser": "Você tem certeza de que deseja excluir {{email}}? Esta ação não pode ser desfeita."
},
"shares": {
"shareProject": "Compartilhar projeto",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Crearea utilizatorului a eșuat",
"badRequest": "Cerere incorectă",
"userNotFound": "Utilizatorul nu a fost găsit",
"failedToDeleteUser": "Ștergerea utilizatorului a eșuat"
"failedToDeleteUser": "Ștergerea utilizatorului a eșuat",
"editUser": "Editează utilizator",
"actions": "Acțiuni",
"passwordOptional": "Lăsați gol pentru a păstra actual",
"failedToUpdateUser": "Actualizarea utilizatorului a eșuat",
"confirmDelete": "Ești sigur că vrei să ștergi acest utilizator?",
"deleteUser": "Șterge utilizator",
"confirmDeleteUser": "Ești sigur că vrei să ștergi {{email}}? Această acțiune nu poate fi anulată."
},
"shares": {
"shareProject": "Partajați proiectul",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Не удалось создать пользователя",
"badRequest": "Неверный запрос",
"userNotFound": "Пользователь не найден",
"failedToDeleteUser": "Не удалось удалить пользователя"
"failedToDeleteUser": "Не удалось удалить пользователя",
"editUser": "Редактировать пользователя",
"actions": "Действия",
"passwordOptional": "Оставьте пустым, чтобы сохранить текущий",
"failedToUpdateUser": "Не удалось обновить пользователя",
"confirmDelete": "Вы уверены, что хотите удалить этого пользователя?",
"deleteUser": "Удалить пользователя",
"confirmDeleteUser": "Вы уверены, что хотите удалить {{email}}? Это действие нельзя отменить."
},
"shares": {
"shareProject": "Поделиться проектом",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Ustvarjanje uporabnika je spodletelo",
"badRequest": "Neveljavna zahteva",
"userNotFound": "Uporabnik ni bil najden",
"failedToDeleteUser": "Brisanje uporabnika je spodletelo"
"failedToDeleteUser": "Brisanje uporabnika je spodletelo",
"editUser": "Uredi uporabnika",
"actions": "Dejanja",
"passwordOptional": "Pustite prazno, da obdržite trenutno",
"failedToUpdateUser": "Posodobitev uporabnika ni uspela",
"confirmDelete": "Ali ste prepričani, da želite izbrisati tega uporabnika?",
"deleteUser": "Izbriši uporabnika",
"confirmDeleteUser": "Ali ste prepričani, da želite izbrisati {{email}}? Te akcije ni mogoče razveljaviti."
},
"shares": {
"shareProject": "Deli projekt",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Misslyckades med att skapa användare",
"badRequest": "Felaktig begäran",
"userNotFound": "Användare hittades inte",
"failedToDeleteUser": "Misslyckades med att ta bort användare"
"failedToDeleteUser": "Misslyckades med att ta bort användare",
"editUser": "Redigera användare",
"actions": "Åtgärder",
"passwordOptional": "Lämna tomt för att behålla nuvarande",
"failedToUpdateUser": "Misslyckades med att uppdatera användare",
"confirmDelete": "Är du säker på att du vill ta bort denna användare?",
"deleteUser": "Ta bort användare",
"confirmDeleteUser": "Är du säker på att du vill ta bort {{email}}? Denna åtgärd kan inte ångras."
},
"shares": {
"shareProject": "Dela projekt",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Kullanıcı oluşturulamadı",
"badRequest": "Geçersiz istek",
"userNotFound": "Kullanıcı bulunamadı",
"failedToDeleteUser": "Kullanıcı silinemedi"
"failedToDeleteUser": "Kullanıcı silinemedi",
"editUser": "Kullanıcıyı Düzenle",
"actions": "Eylemler",
"passwordOptional": "Mevcut şifreyi korumak için boş bırakın",
"failedToUpdateUser": "Kullanıcıyı güncellemeye başarısız olundu",
"confirmDelete": "Bu kullanıcıyı silmek istediğinize emin misiniz?",
"deleteUser": "Kullanıcıyı Sil",
"confirmDeleteUser": "{{email}}'yi silmek istediğinize emin misiniz? Bu işlem geri alınamaz."
},
"shares": {
"shareProject": "Projeyi paylaş",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Не вдалося створити користувача",
"badRequest": "Неправильний запит",
"userNotFound": "Користувача не знайдено",
"failedToDeleteUser": "Не вдалося видалити користувача"
"failedToDeleteUser": "Не вдалося видалити користувача",
"editUser": "Редагувати користувача",
"actions": "Дії",
"passwordOptional": "Залиште порожнім, щоб зберегти поточний",
"failedToUpdateUser": "Не вдалося оновити користувача",
"confirmDelete": "Ви впевнені, що хочете видалити цього користувача?",
"deleteUser": "Видалити користувача",
"confirmDeleteUser": "Ви впевнені, що хочете видалити {{email}}? Цю дію не можна скасувати."
},
"shares": {
"shareProject": "Поділитися проектом",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "Không thể tạo người dùng",
"badRequest": "Yêu cầu không hợp lệ",
"userNotFound": "Không tìm thấy người dùng",
"failedToDeleteUser": "Không thể xóa người dùng"
"failedToDeleteUser": "Không thể xóa người dùng",
"editUser": "Chỉnh sửa người dùng",
"actions": "Hành động",
"passwordOptional": "Để trống để giữ nguyên",
"failedToUpdateUser": "Cập nhật người dùng không thành công",
"confirmDelete": "Bạn có chắc chắn muốn xóa người dùng này không?",
"deleteUser": "Xóa người dùng",
"confirmDeleteUser": "Bạn có chắc chắn muốn xóa {{email}}? Hành động này không thể hoàn tác."
},
"shares": {
"shareProject": "Chia sẻ dự án",

View file

@ -954,7 +954,14 @@
"failedToCreateUser": "创建用户失败",
"badRequest": "错误的请求",
"userNotFound": "用户未找到",
"failedToDeleteUser": "删除用户失败"
"failedToDeleteUser": "删除用户失败",
"editUser": "编辑用户",
"actions": "操作",
"passwordOptional": "留空以保持当前密码",
"failedToUpdateUser": "更新用户失败",
"confirmDelete": "您确定要删除此用户吗?",
"deleteUser": "删除用户",
"confirmDeleteUser": "您确定要删除 {{email}} 吗?此操作无法撤销。"
},
"shares": {
"shareProject": "分享项目",