huridocs/uwazi

View on GitHub
app/react/V2/Routes/Settings/Users/Users.tsx

Summary

Maintainability
B
6 hrs
Test Coverage
/* eslint-disable max-lines */
import React, { useState } from 'react';
import { IncomingHttpHeaders } from 'http';
import { ActionFunction, LoaderFunction, useFetcher, useLoaderData } from 'react-router-dom';
import { Row } from '@tanstack/react-table';
import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes';
import { Translate } from 'app/I18N';
import { Button, ConfirmationModal, Table, Tabs } from 'V2/Components/UI';
import * as usersAPI from 'V2/api/users';
import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent';
import {
  UserFormSidepanel,
  GroupFormSidepanel,
  getUsersColumns,
  getGroupsColumns,
  ListOfItems,
} from './components';
import { useHandleNotifications } from './useHandleNotifications';
import { FormIntent } from './types';

type ActiveTab = 'Groups' | 'Users';

// eslint-disable-next-line max-statements
const Users = () => {
  const { users, groups } =
    (useLoaderData() as { users: ClientUserSchema[]; groups: ClientUserGroupSchema[] }) || [];

  const [activeTab, setActiveTab] = useState<ActiveTab>('Users');
  const [selectedUsers, setSelectedUsers] = useState<Row<ClientUserSchema>[]>([]);
  const [selectedGroups, setSelectedGroups] = useState<Row<ClientUserGroupSchema>[]>([]);
  const [sidepanelData, setSidepanelData] = useState<
    ClientUserSchema | ClientUserGroupSchema | undefined
  >();
  const [showSidepanel, setShowSidepanel] = useState(false);
  const [showConfirmationModal, setShowConfirmationModal] = useState(false);
  const [confirmationModalProps, setConfirmationModalProps] = useState({
    header: 'Delete',
    body: 'Do you want to delete?',
  });
  const [bulkActionIntent, setBulkActionIntent] = useState<FormIntent>('delete-users');

  const fetcher = useFetcher();

  useHandleNotifications();

  const usersTableColumns = getUsersColumns((user: ClientUserSchema) => {
    setShowSidepanel(true);
    setSidepanelData(user);
  });

  const groupsTableColumns = getGroupsColumns((group: ClientUserGroupSchema) => {
    setShowSidepanel(true);
    setSidepanelData(group);
  });

  const handleBulkAction = () => {
    const formData = new FormData();
    formData.set('intent', bulkActionIntent);

    if (activeTab === 'Users') {
      formData.set('data', JSON.stringify(selectedUsers.map(user => user.original)));
    } else {
      formData.set('data', JSON.stringify(selectedGroups.map(group => group.original)));
    }

    fetcher.submit(formData, { method: 'post' });
  };

  return (
    <div className="tw-content" style={{ width: '100%', overflowY: 'auto' }}>
      <SettingsContent>
        <SettingsContent.Header title="Users & Groups" />

        <SettingsContent.Body>
          <Tabs
            onTabSelected={tab => {
              setActiveTab(tab as ActiveTab);
              setSelectedUsers([]);
              setSelectedGroups([]);
              setSidepanelData(undefined);
            }}
          >
            <Tabs.Tab id="Users" label={<Translate>Users</Translate>}>
              <Table<ClientUserSchema>
                columns={usersTableColumns}
                data={users}
                title={<Translate>Users</Translate>}
                enableSelection
                onSelection={setSelectedUsers}
                initialState={{ sorting: [{ id: 'username', desc: false }] }}
              />
            </Tabs.Tab>

            <Tabs.Tab id="Groups" label={<Translate>Groups</Translate>}>
              <Table<ClientUserGroupSchema>
                columns={groupsTableColumns}
                data={groups}
                title={<Translate>Groups</Translate>}
                enableSelection
                onSelection={setSelectedGroups}
                initialState={{ sorting: [{ id: 'name', desc: false }] }}
              />
            </Tabs.Tab>
          </Tabs>
        </SettingsContent.Body>

        <SettingsContent.Footer>
          <div className="flex gap-2">
            {selectedUsers.length ? (
              <>
                <Button
                  styling="light"
                  onClick={() => {
                    setConfirmationModalProps({
                      header: 'Reset passwords',
                      body: 'Do you want reset the password for the following users?',
                    });
                    setBulkActionIntent('bulk-reset-password');
                    setShowConfirmationModal(true);
                  }}
                >
                  <Translate>Reset Password</Translate>
                </Button>

                <Button
                  styling="light"
                  onClick={() => {
                    setConfirmationModalProps({
                      header: 'Reset 2FA',
                      body: 'Do you want disable 2FA for the following users?',
                    });
                    setBulkActionIntent('bulk-reset-2fa');
                    setShowConfirmationModal(true);
                  }}
                >
                  <Translate>Reset 2FA</Translate>
                </Button>
              </>
            ) : undefined}

            {selectedUsers.length || selectedGroups.length ? (
              <Button
                color="error"
                onClick={() => {
                  setConfirmationModalProps({
                    header: 'Delete',
                    body: 'Do you want to delete the following items?',
                  });
                  setBulkActionIntent(activeTab === 'Users' ? 'delete-users' : 'delete-groups');
                  setShowConfirmationModal(true);
                }}
              >
                <Translate>Delete</Translate>
              </Button>
            ) : undefined}

            {!selectedUsers.length && !selectedGroups.length ? (
              <Button
                onClick={() => {
                  setSidepanelData(undefined);
                  setShowSidepanel(true);
                }}
              >
                {activeTab === 'Users' ? (
                  <Translate>Add user</Translate>
                ) : (
                  <Translate>Add group</Translate>
                )}
              </Button>
            ) : undefined}
          </div>
        </SettingsContent.Footer>
      </SettingsContent>

      {activeTab === 'Users' ? (
        <UserFormSidepanel
          selectedUser={sidepanelData as ClientUserSchema}
          showSidepanel={showSidepanel}
          setShowSidepanel={setShowSidepanel}
          setSelected={setSidepanelData}
          users={users}
          groups={groups}
        />
      ) : (
        <GroupFormSidepanel
          selectedGroup={sidepanelData as ClientUserGroupSchema}
          showSidepanel={showSidepanel}
          setShowSidepanel={setShowSidepanel}
          setSelected={setSidepanelData}
          users={users}
          groups={groups}
        />
      )}

      {showConfirmationModal && (
        <ConfirmationModal
          header={confirmationModalProps.header}
          warningText={confirmationModalProps.body}
          body={<ListOfItems items={selectedUsers.length ? selectedUsers : selectedGroups} />}
          onAcceptClick={() => {
            handleBulkAction();
            setShowConfirmationModal(false);
            setSelectedGroups([]);
            setSelectedUsers([]);
          }}
          onCancelClick={() => setShowConfirmationModal(false)}
          dangerStyle
        />
      )}
    </div>
  );
};

const usersLoader =
  (headers?: IncomingHttpHeaders): LoaderFunction =>
  async () => {
    const users = await usersAPI.get(headers);
    const groups = await usersAPI.getUserGroups(headers);
    return { users, groups };
  };

const userAction =
  (): ActionFunction =>
  async ({ request }) => {
    const formData = await request.formData();
    const formIntent = formData.get('intent') as FormIntent;

    const formValues = JSON.parse(formData.get('data') as string);

    switch (formIntent) {
      case 'new-user':
        return usersAPI.newUser(formValues);
      case 'edit-user':
        return usersAPI.updateUser(formValues);
      case 'delete-users':
        return usersAPI.deleteUser(formValues);
      case 'new-group':
      case 'edit-group':
        return usersAPI.saveGroup(formValues);
      case 'delete-groups':
        return usersAPI.deleteGroup(formValues);
      case 'unlock-user':
        return usersAPI.unlockAccount(formValues);
      case 'reset-password':
      case 'bulk-reset-password':
        return usersAPI.resetPassword(formValues);
      case 'reset-2fa':
      case 'bulk-reset-2fa':
        return usersAPI.reset2FA(formValues);
      default:
        return null;
    }
  };

export { Users, usersLoader, userAction };