feat: version 2.10.3 -- reenviar correo de invitacion/verificacion desde Usuarios

Nueva opcion "Reenviar correo" en el menu de 3 puntos de Organizacion -> Usuarios, para
usuarios en estado 'invited' o 'pending_verification'. Reusa las mismas columnas
(verification_token/verification_token_expires) y plantillas que el envio original
(inviteUser / auth/service.js#register), solo genera un token nuevo -- el anterior pudo
vencer o perderse en spam.

De paso se corrige que el estado "invited" no tenia rama propia en el menu de acciones (caia
en el branch generico de editar/activar-desactivar, que no aplica a una cuenta placeholder sin
clave real todavia) ni traduccion de su badge (user_status_invited faltaba en i18n).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-12 16:02:24 -05:00
parent 98d9196791
commit 2d1bfc0bfd
11 changed files with 90 additions and 8 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "project-linkedcorp-frontend",
"private": true,
"version": "2.10.2",
"version": "2.10.3",
"type": "module",
"scripts": {
"dev": "vite",
+6
View File
@@ -43,10 +43,13 @@ export const messages = {
user_status_pending_verification: 'Pendiente de verificar correo',
user_status_pending_approval: 'Pendiente de aprobacion',
user_status_active: 'Activo',
user_status_invited: 'Invitacion pendiente',
user_approve: 'Aprobar',
user_reject: 'Rechazar',
user_approve_title: 'Aprobar usuario',
user_reject_confirm_msg: '¿Seguro que deseas rechazar esta solicitud de registro? Se eliminara permanentemente.',
user_resend: 'Reenviar correo',
user_resend_sent: 'Correo reenviado',
config_company: 'Empresa',
company_name: 'Nombre de la empresa',
@@ -383,10 +386,13 @@ export const messages = {
user_status_pending_verification: 'Pending email verification',
user_status_pending_approval: 'Pending approval',
user_status_active: 'Active',
user_status_invited: 'Invite pending',
user_approve: 'Approve',
user_reject: 'Reject',
user_approve_title: 'Approve user',
user_reject_confirm_msg: 'Are you sure you want to reject this registration request? It will be permanently deleted.',
user_resend: 'Resend email',
user_resend_sent: 'Email resent',
config_company: 'Company',
company_name: 'Company name',
+17 -4
View File
@@ -1,9 +1,9 @@
import { useState } from 'react';
import { Plus, Save, UserX, UserCheck, Check, X as XIcon } from 'lucide-react';
import { Plus, Save, UserX, UserCheck, Check, X as XIcon, Mail } from 'lucide-react';
import { useUi } from '../../context/ui-context.jsx';
import { useAuth } from '../../context/auth-context.jsx';
import { useAsync, useAction } from '../../hooks/useAsync.js';
import { listUsers, listRoles, createUser, updateUser, setUserActive, approveUser, rejectUser } from '../../services/users.js';
import { listUsers, listRoles, createUser, updateUser, setUserActive, approveUser, rejectUser, resendUserEmail } from '../../services/users.js';
import { listDepartments } from '../../services/departments.js';
import { ListView } from '../../components/ListView.jsx';
import { SearchInput } from '../../components/SearchInput.jsx';
@@ -19,7 +19,7 @@ function flatten(nodes, acc = []) {
return acc;
}
const STATUS_COLOR = { pending_verification: 'muted', pending_approval: 'warning', active: 'success' };
const STATUS_COLOR = { pending_verification: 'muted', pending_approval: 'warning', active: 'success', invited: 'muted' };
const EMPTY_FORM = { name: '', email: '', roleId: '', departmentId: '', position: '', password: '' };
export default function UsersPanel() {
@@ -92,6 +92,13 @@ export default function UsersPanel() {
});
}
function resendEmail(user) {
runAction(async () => {
await resendUserEmail(user.id);
pushToast({ tone: 'success', message: t('user_resend_sent') });
});
}
function confirmReject(user) {
openDialog({
tone: 'warning',
@@ -140,7 +147,13 @@ export default function UsersPanel() {
];
}
if (u.status === 'pending_verification') {
return [{ label: t('user_reject'), icon: XIcon, danger: true, onClick: () => confirmReject(u) }];
return [
{ label: t('user_resend'), icon: Mail, onClick: () => resendEmail(u) },
{ label: t('user_reject'), icon: XIcon, danger: true, onClick: () => confirmReject(u) },
];
}
if (u.status === 'invited') {
return [{ label: t('user_resend'), icon: Mail, onClick: () => resendEmail(u) }];
}
return [
{ label: t('common_edit'), onClick: () => startEdit(u) },
+1
View File
@@ -7,3 +7,4 @@ export const updateUser = (id, data) => api.put(`/users/${id}`, data);
export const setUserActive = (id, isActive) => api.patch(`/users/${id}/active`, { isActive });
export const approveUser = (id, data) => api.post(`/users/${id}/approve`, data);
export const rejectUser = (id) => api.post(`/users/${id}/reject`);
export const resendUserEmail = (id) => api.post(`/users/${id}/resend`);