mirror of
https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
synced 2025-10-29 09:22:21 +00:00
[WIFI-12223] User table state fix, with label correction and API logic update
Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "2.9.0(2)",
|
||||
"version": "2.9.0(3)",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ucentral-client",
|
||||
"version": "2.9.0(2)",
|
||||
"version": "2.9.0(3)",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@chakra-ui/icons": "^2.0.11",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ucentral-client",
|
||||
"version": "2.9.0(2)",
|
||||
"version": "2.9.0(3)",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"main": "index.tsx",
|
||||
|
||||
@@ -801,6 +801,7 @@
|
||||
"reset_password": "Passwort zurücksetzen",
|
||||
"sign_in": "Einloggen",
|
||||
"sms_instructions": "Sie sollten bald einen 6-stelligen Code auf Ihrem Telefon erhalten. Bitte geben Sie es unten ein, um sich anzumelden",
|
||||
"suspended_error": "Konto gesperrt, wenden Sie sich bitte an Ihren Administrator",
|
||||
"verification": "Bestätigen Sie Ihre Anmeldung",
|
||||
"waiting_for_email_verification": "Konto noch nicht per E-Mail validiert. Bitte sehen Sie in Ihrem Posteingang nach oder bitten Sie Ihren Administrator, eine Bestätigung erneut zu senden",
|
||||
"welcome_back": "Willkommen zurück!",
|
||||
@@ -1057,9 +1058,11 @@
|
||||
"previous_page": "Vorherige Seite"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "E-Mail nicht validiert",
|
||||
"error_fetching": "Fehler beim Abrufen der Benutzerinformationen: {{e}}",
|
||||
"password": "Passwort",
|
||||
"role": "Rolle",
|
||||
"suspended": "Suspendiert",
|
||||
"title": "Nutzer"
|
||||
},
|
||||
"users": {
|
||||
|
||||
@@ -801,6 +801,7 @@
|
||||
"reset_password": "Reset Password",
|
||||
"sign_in": "Sign In",
|
||||
"sms_instructions": "You should receive a 6-digit code on your phone soon. Please enter it below to login",
|
||||
"suspended_error": "Suspended account, please contact your administrator",
|
||||
"verification": "Verify your login",
|
||||
"waiting_for_email_verification": "Account not yet email validated. Please look at your inbox or ask your administrator to resend a validation",
|
||||
"welcome_back": "Welcome Back!",
|
||||
@@ -1057,9 +1058,11 @@
|
||||
"previous_page": "Previous Page"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "email not validated",
|
||||
"error_fetching": "Error fetching user information: {{e}}",
|
||||
"password": "Password",
|
||||
"role": "Role",
|
||||
"suspended": "suspended",
|
||||
"title": "User"
|
||||
},
|
||||
"users": {
|
||||
|
||||
@@ -801,6 +801,7 @@
|
||||
"reset_password": "Restablecer la contraseña",
|
||||
"sign_in": "Registrarse",
|
||||
"sms_instructions": "Debería recibir un código de 6 dígitos en su teléfono pronto. Por favor, introdúzcalo a continuación para iniciar sesión",
|
||||
"suspended_error": "Cuenta suspendida, comuníquese con su administrador",
|
||||
"verification": "Verifica tu inicio de sesión",
|
||||
"waiting_for_email_verification": "Cuenta aún no validada por correo electrónico. Mire su bandeja de entrada o solicite a su administrador que vuelva a enviar una validación.",
|
||||
"welcome_back": "¡Dar una buena acogida!",
|
||||
@@ -1057,9 +1058,11 @@
|
||||
"previous_page": "Página anterior"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "correo electrónico no validado",
|
||||
"error_fetching": "Error al obtener la información del usuario: {{e}}",
|
||||
"password": "Contraseña",
|
||||
"role": "papel",
|
||||
"suspended": "Suspendido",
|
||||
"title": "Usuario"
|
||||
},
|
||||
"users": {
|
||||
|
||||
@@ -801,6 +801,7 @@
|
||||
"reset_password": "Réinitialiser le mot de passe",
|
||||
"sign_in": "se connecter",
|
||||
"sms_instructions": "Vous devriez bientôt recevoir un code à 6 chiffres sur votre téléphone. Veuillez le saisir ci-dessous pour vous connecter",
|
||||
"suspended_error": "Compte suspendu, veuillez contacter votre administrateur",
|
||||
"verification": "Vérifiez votre connexion",
|
||||
"waiting_for_email_verification": "Compte pas encore e-mail validé. Veuillez consulter votre boîte de réception ou demander à votre administrateur de renvoyer une validation",
|
||||
"welcome_back": "Nous saluons le retour!",
|
||||
@@ -1057,9 +1058,11 @@
|
||||
"previous_page": "Page précédente"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "Mail non valide",
|
||||
"error_fetching": "Erreur lors de la récupération des informations utilisateur : {{e}}",
|
||||
"password": "Mot de passe",
|
||||
"role": "Rôle",
|
||||
"suspended": "Suspendu",
|
||||
"title": "Utilisateur"
|
||||
},
|
||||
"users": {
|
||||
|
||||
@@ -801,6 +801,7 @@
|
||||
"reset_password": "Redefinir senha",
|
||||
"sign_in": "assinar em",
|
||||
"sms_instructions": "Você deve receber um código de 6 dígitos em seu telefone em breve. Por favor, insira-o abaixo para fazer login",
|
||||
"suspended_error": "Conta suspensa, entre em contato com seu administrador",
|
||||
"verification": "Verifique seu login",
|
||||
"waiting_for_email_verification": "Conta ainda não validada por e-mail. Verifique sua caixa de entrada ou peça ao administrador para reenviar uma validação",
|
||||
"welcome_back": "Bem vindo de volta!",
|
||||
@@ -1057,9 +1058,11 @@
|
||||
"previous_page": "Página anterior"
|
||||
},
|
||||
"user": {
|
||||
"email_not_validated": "e-mail não validado",
|
||||
"error_fetching": "Erro ao buscar informações do usuário: {{e}}",
|
||||
"password": "Senha",
|
||||
"role": "Função",
|
||||
"suspended": "Suspenso",
|
||||
"title": "Do utilizador"
|
||||
},
|
||||
"users": {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Flex, ModalHeader as Header, Spacer } from '@chakra-ui/react';
|
||||
import { Flex, HStack, ModalHeader as Header, Spacer } from '@chakra-ui/react';
|
||||
|
||||
export interface ModalHeaderProps {
|
||||
title: string;
|
||||
left?: React.ReactNode;
|
||||
right: React.ReactNode;
|
||||
}
|
||||
|
||||
const _ModalHeader: React.FC<ModalHeaderProps> = ({ title, right }) => (
|
||||
const _ModalHeader: React.FC<ModalHeaderProps> = ({ title, left, right }) => (
|
||||
<Header>
|
||||
<Flex justifyContent="center" alignItems="center" maxW="100%" px={1}>
|
||||
{title}
|
||||
<HStack spacing={2} ml={2}>
|
||||
{left ?? null}
|
||||
</HStack>
|
||||
<Spacer />
|
||||
{right}
|
||||
</Flex>
|
||||
|
||||
@@ -8,6 +8,7 @@ export type ModalProps = {
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
topRightButtons?: React.ReactNode;
|
||||
tags?: React.ReactNode;
|
||||
options?: {
|
||||
modalSize?: 'sm' | 'md' | 'lg';
|
||||
maxWidth?: LayoutProps['maxWidth'];
|
||||
@@ -15,7 +16,7 @@ export type ModalProps = {
|
||||
children: React.ReactElement;
|
||||
};
|
||||
|
||||
const _Modal = ({ isOpen, onClose, title, topRightButtons, options, children }: ModalProps) => {
|
||||
const _Modal = ({ isOpen, onClose, title, topRightButtons, tags, options, children }: ModalProps) => {
|
||||
const maxWidth = React.useMemo(() => {
|
||||
if (options?.maxWidth) return options.maxWidth;
|
||||
if (options?.modalSize === 'sm') return undefined;
|
||||
@@ -32,6 +33,7 @@ const _Modal = ({ isOpen, onClose, title, topRightButtons, options, children }:
|
||||
<ModalContent maxWidth={maxWidth}>
|
||||
<ModalHeader
|
||||
title={title}
|
||||
left={tags}
|
||||
right={
|
||||
<HStack spacing={2}>
|
||||
{topRightButtons}
|
||||
|
||||
@@ -7,17 +7,32 @@ import { AxiosError } from 'models/Axios';
|
||||
import { Firmware } from 'models/Firmware';
|
||||
import { Note } from 'models/Note';
|
||||
|
||||
const getAvailableFirmwareBatch = async (deviceType: string, limit: number, offset: number) =>
|
||||
axiosFms
|
||||
.get(`firmwares?deviceType=${deviceType}&limit=${limit}&offset=${offset}`)
|
||||
.then(({ data }: { data: { firmwares: Firmware[] } }) => data);
|
||||
|
||||
const getAllAvailableFirmware = async (deviceType: string) => {
|
||||
const limit = 500;
|
||||
let offset = 0;
|
||||
let data: { firmwares: Firmware[] } = { firmwares: [] };
|
||||
let lastResponse: { firmwares: Firmware[] } = { firmwares: [] };
|
||||
do {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
lastResponse = await getAvailableFirmwareBatch(deviceType, limit, offset);
|
||||
data = {
|
||||
firmwares: [...data.firmwares, ...lastResponse.firmwares],
|
||||
};
|
||||
offset += 500;
|
||||
} while (lastResponse.firmwares.length === 500);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useGetAvailableFirmware = ({ deviceType }: { deviceType: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
|
||||
return useQuery(
|
||||
['get-device-profile'],
|
||||
() =>
|
||||
axiosFms
|
||||
.get(`firmwares?deviceType=${deviceType}&limit=10000&offset=0`)
|
||||
.then(({ data }: { data: { firmwares: Firmware[] } }) => data),
|
||||
{
|
||||
return useQuery(['firmware'], () => getAllAvailableFirmware(deviceType), {
|
||||
enabled: deviceType !== '',
|
||||
onError: (e: AxiosError) => {
|
||||
if (!toast.isActive('firmware-fetching-error'))
|
||||
@@ -34,8 +49,7 @@ export const useGetAvailableFirmware = ({ deviceType }: { deviceType: string })
|
||||
position: 'top-right',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateDeviceToLatest = ({ serialNumber, compatible }: { serialNumber: string; compatible: string }) =>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryClient, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { axiosSec } from 'constants/axiosInstances';
|
||||
import { AxiosError } from 'models/Axios';
|
||||
@@ -58,11 +58,23 @@ export type User = {
|
||||
waitingForEmailCheck: boolean;
|
||||
};
|
||||
|
||||
const getAvatarPromises = (userList: User[]) => {
|
||||
const getAvatarPromises = (userList: User[], queryClient: QueryClient) => {
|
||||
const promises = userList.map(async (user) => {
|
||||
if (user.avatar !== '' && user.avatar !== '0') {
|
||||
return axiosSec.get(`avatar/${user.id}?cache=${user.avatar}`, {
|
||||
// If the avatar is already in the cache, return it
|
||||
const cachedAvatar = queryClient.getQueryData(['avatar', user.id, user.avatar]);
|
||||
if (cachedAvatar) return cachedAvatar;
|
||||
|
||||
return axiosSec
|
||||
.get(`avatar/${user.id}?cache=${user.avatar}`, {
|
||||
responseType: 'arraybuffer',
|
||||
})
|
||||
.then((response) => {
|
||||
queryClient.setQueryData(['avatar', user.id, user.avatar], response);
|
||||
return response;
|
||||
})
|
||||
.catch((e) => {
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
return Promise.resolve('');
|
||||
@@ -71,10 +83,35 @@ const getAvatarPromises = (userList: User[]) => {
|
||||
return promises;
|
||||
};
|
||||
|
||||
const getUsers = async () => {
|
||||
const users = await axiosSec.get('users').then(({ data }) => data.users as User[]);
|
||||
const getBatchUsers = async (offset: number, limit: number) => {
|
||||
const users = await axiosSec
|
||||
.get(`users?offset=${offset}&limit=${limit}&withExtendedInfo=true`)
|
||||
.then(({ data }) => data.users as User[]);
|
||||
|
||||
const avatars = await Promise.allSettled(getAvatarPromises(users)).then((results) =>
|
||||
return users;
|
||||
};
|
||||
|
||||
const getAllUsers = async () => {
|
||||
let users: User[] = [];
|
||||
let offset = 0;
|
||||
const limit = 500;
|
||||
let lastResponseLength = 0;
|
||||
|
||||
do {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const response = await getBatchUsers(offset, limit);
|
||||
users = [...users, ...response];
|
||||
offset += limit;
|
||||
lastResponseLength = response.length;
|
||||
} while (lastResponseLength === limit);
|
||||
|
||||
return users;
|
||||
};
|
||||
|
||||
const getUsers = async (queryClient: QueryClient) => {
|
||||
const users = await getAllUsers();
|
||||
|
||||
const avatars = await Promise.allSettled(getAvatarPromises(users, queryClient)).then((results) =>
|
||||
results.map((response) => {
|
||||
if (response.status === 'fulfilled' && response?.value !== '') {
|
||||
const base64 = btoa(
|
||||
@@ -93,8 +130,10 @@ const getUsers = async () => {
|
||||
export const useGetUsers = () => {
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useQuery(['users'], getUsers, {
|
||||
return useQuery(['users'], () => getUsers(queryClient), {
|
||||
staleTime: 30 * 1000,
|
||||
onError: (e: AxiosError) => {
|
||||
if (!toast.isActive('users-fetching-error'))
|
||||
toast({
|
||||
@@ -118,7 +157,7 @@ export const useGetUser = ({ id, enabled }: { id: string; enabled: boolean }) =>
|
||||
const toast = useToast();
|
||||
|
||||
return useQuery(
|
||||
['get-user', id],
|
||||
['users', id],
|
||||
() => axiosSec.get(`user/${id}?withExtendedInfo=true`).then(({ data }) => data as User),
|
||||
{
|
||||
enabled,
|
||||
@@ -173,16 +212,41 @@ export const useSendUserEmailValidation = ({ id, refresh }: { id: string; refres
|
||||
},
|
||||
});
|
||||
};
|
||||
export const useSuspendUser = ({ id }: { id: string }) =>
|
||||
useMutation((isSuspended: boolean) =>
|
||||
export const useSuspendUser = ({ id }: { id: string }) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
(isSuspended: boolean) =>
|
||||
axiosSec.put(`user/${id}`, {
|
||||
suspended: isSuspended,
|
||||
}),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(['users']);
|
||||
},
|
||||
},
|
||||
);
|
||||
export const useResetMfa = ({ id }: { id: string }) => useMutation(() => axiosSec.put(`user/${id}?resetMFA=true`, {}));
|
||||
};
|
||||
|
||||
export const useResetPassword = ({ id }: { id: string }) =>
|
||||
useMutation(() => axiosSec.put(`user/${id}?forgotPassword=true`, {}));
|
||||
export const useResetMfa = ({ id }: { id: string }) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(() => axiosSec.put(`user/${id}?resetMFA=true`, {}), {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(['users']);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useResetPassword = ({ id }: { id: string }) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(() => axiosSec.put(`user/${id}?forgotPassword=true`, {}), {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(['users']);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const deleteUser = async (userId: string) => axiosSec.delete(`/user/${userId}`);
|
||||
export const useDeleteUser = () => {
|
||||
|
||||
@@ -48,7 +48,10 @@ const _LoginForm: React.FC<_LoginFormProps> = ({ setActiveForm }) => {
|
||||
const displayError = useMemo(() => {
|
||||
const loginError: AxiosError = error as AxiosError;
|
||||
|
||||
if (loginError?.response?.data?.ErrorCode === 4) return t('login.waiting_for_email_verification');
|
||||
if (loginError?.response?.data?.ErrorCode === 5) return t('login.waiting_for_email_verification');
|
||||
if (loginError?.response?.data?.ErrorCode === 15) {
|
||||
return t('login.suspended_error');
|
||||
}
|
||||
return t('login.invalid_credentials');
|
||||
}, [t, error]);
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ interface Props {
|
||||
isSuspended: boolean;
|
||||
isWaitingForCheck: boolean;
|
||||
refresh: () => void;
|
||||
isDisabled?: boolean;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
const UserActions: React.FC<Props> = ({ id, isSuspended, isWaitingForCheck, refresh }) => {
|
||||
const UserActions = ({ id, isSuspended, isWaitingForCheck, refresh, size = 'sm', isDisabled }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
const { mutateAsync: sendValidation } = useSendUserEmailValidation({ id, refresh });
|
||||
@@ -76,9 +78,16 @@ const UserActions: React.FC<Props> = ({ id, isSuspended, isWaitingForCheck, refr
|
||||
return (
|
||||
<Menu>
|
||||
<Tooltip label={t('commands.other')}>
|
||||
<MenuButton as={IconButton} aria-label="Commands" icon={<Wrench size={20} />} size="sm" ml={2} />
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label="Commands"
|
||||
icon={<Wrench size={20} />}
|
||||
size={size}
|
||||
ml={2}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</Tooltip>
|
||||
<MenuList>
|
||||
<MenuList fontSize="md">
|
||||
<MenuItem onClick={handleSuspendClick}>
|
||||
{isSuspended ? t('users.reactivate_user') : t('users.suspend')}
|
||||
</MenuItem>
|
||||
|
||||
@@ -62,7 +62,7 @@ const UpdateUserForm = ({ editing, isOpen, onClose, selectedUser, formRef }: Pro
|
||||
|
||||
useEffect(() => {
|
||||
setFormKey(uuid());
|
||||
}, [isOpen]);
|
||||
}, [isOpen, editing]);
|
||||
|
||||
return (
|
||||
<Formik
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Spinner, Center, useDisclosure, useBoolean } from '@chakra-ui/react';
|
||||
import { Spinner, Center, useDisclosure, useBoolean, Tag } from '@chakra-ui/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EditButton } from '../../../../components/Buttons/EditButton';
|
||||
import { SaveButton } from '../../../../components/Buttons/SaveButton';
|
||||
import { ConfirmCloseAlertModal } from '../../../../components/Modals/ConfirmCloseAlert';
|
||||
import { Modal } from '../../../../components/Modals/Modal';
|
||||
import ActionsDropdown from '../ActionsDropdown';
|
||||
import UpdateUserForm from './Form';
|
||||
import { ToggleEditButton } from 'components/Buttons/ToggleEditButton';
|
||||
import { useGetUser, User } from 'hooks/Network/Users';
|
||||
import { useFormRef } from 'hooks/useFormRef';
|
||||
|
||||
@@ -19,10 +21,11 @@ type Props = {
|
||||
const EditUserModal = ({ isOpen, onClose, userId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [editing, setEditing] = useBoolean();
|
||||
const queryClient = useQueryClient();
|
||||
const { isOpen: showConfirm, onOpen: openConfirm, onClose: closeConfirm } = useDisclosure();
|
||||
const { form, formRef } = useFormRef<User>();
|
||||
const canFetchUser = userId !== '' && isOpen;
|
||||
const { data: user, isFetching } = useGetUser({ id: userId ?? '', enabled: canFetchUser });
|
||||
const { data: user, isFetching, refetch } = useGetUser({ id: userId ?? '', enabled: canFetchUser });
|
||||
|
||||
const closeModal = () => (form.dirty ? openConfirm() : onClose());
|
||||
|
||||
@@ -31,6 +34,11 @@ const EditUserModal = ({ isOpen, onClose, userId }: Props) => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
refetch();
|
||||
queryClient.invalidateQueries(['users']);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) setEditing.off();
|
||||
}, [isOpen]);
|
||||
@@ -40,15 +48,40 @@ const EditUserModal = ({ isOpen, onClose, userId }: Props) => {
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
title={t('crud.edit_obj', { obj: t('user.title') })}
|
||||
title={user?.name ?? t('crud.edit_obj', { obj: t('user.title') })}
|
||||
tags={
|
||||
<>
|
||||
{user?.suspended ? (
|
||||
<Tag colorScheme="yellow" size="lg">
|
||||
{t('user.suspended')}
|
||||
</Tag>
|
||||
) : null}
|
||||
{user?.waitingForEmailCheck ? (
|
||||
<Tag colorScheme="blue" size="lg">
|
||||
{t('user.email_not_validated')}
|
||||
</Tag>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
topRightButtons={
|
||||
<>
|
||||
<SaveButton
|
||||
onClick={form.submitForm}
|
||||
isLoading={form.isSubmitting}
|
||||
isDisabled={!editing || !form.isValid || !form.dirty}
|
||||
hidden={!editing}
|
||||
/>
|
||||
<EditButton ml={2} isDisabled={editing} onClick={setEditing.toggle} isCompact />
|
||||
<ToggleEditButton ml={2} isEditing={editing} toggleEdit={setEditing.toggle} isDirty={form.dirty} />
|
||||
{user ? (
|
||||
<ActionsDropdown
|
||||
id={user?.id}
|
||||
isSuspended={user?.suspended}
|
||||
isWaitingForCheck={user?.waitingForEmailCheck}
|
||||
refresh={refresh}
|
||||
size="md"
|
||||
isDisabled={editing}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -141,7 +141,7 @@ const UserTable = () => {
|
||||
<Box overflowX="auto" w="100%">
|
||||
<DataTable
|
||||
columns={columns as Column<object>[]}
|
||||
data={users?.filter((curr) => curr.email !== user?.email) ?? []}
|
||||
data={users ?? []}
|
||||
isLoading={isFetching}
|
||||
obj={t('users.title')}
|
||||
sortBy={[{ id: 'email', desc: false }]}
|
||||
|
||||
Reference in New Issue
Block a user