RocketChat/Rocket.Chat

View on GitHub
apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx

Summary

Maintainability
F
3 days
Test Coverage
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import {
    useSetModal,
    useToastMessageDispatch,
    useUserId,
    useSetting,
    usePermission,
    useRole,
    useEndpoint,
    useMethod,
    useTranslation,
    useRouter,
} from '@rocket.chat/ui-contexts';
import React, { useCallback, useState, useEffect } from 'react';

import { LivechatInquiry } from '../../../../../../../app/livechat/client/collections/LivechatInquiry';
import { LegacyRoomManager } from '../../../../../../../app/ui-utils/client';
import PlaceChatOnHoldModal from '../../../../../../../ee/app/livechat-enterprise/client/components/modals/PlaceChatOnHoldModal';
import { useHasLicenseModule } from '../../../../../../../ee/client/hooks/useHasLicenseModule';
import CloseChatModal from '../../../../../../components/Omnichannel/modals/CloseChatModal';
import CloseChatModalData from '../../../../../../components/Omnichannel/modals/CloseChatModalData';
import ForwardChatModal from '../../../../../../components/Omnichannel/modals/ForwardChatModal';
import ReturnChatQueueModal from '../../../../../../components/Omnichannel/modals/ReturnChatQueueModal';
import TranscriptModal from '../../../../../../components/Omnichannel/modals/TranscriptModal';
import { useIsRoomOverMacLimit } from '../../../../../../hooks/omnichannel/useIsRoomOverMacLimit';
import { useOmnichannelRouteConfig } from '../../../../../../hooks/omnichannel/useOmnichannelRouteConfig';
import { quickActionHooks } from '../../../../../../ui';
import { useOmnichannelRoom } from '../../../../contexts/RoomContext';
import type { QuickActionsActionConfig } from '../../../../lib/quickActions';
import { QuickActionsEnum } from '../../../../lib/quickActions';
import { usePutChatOnHoldMutation } from './usePutChatOnHoldMutation';
import { useReturnChatToQueueMutation } from './useReturnChatToQueueMutation';

export const useQuickActions = (): {
    quickActions: QuickActionsActionConfig[];
    actionDefault: (actionId: string) => void;
} => {
    const room = useOmnichannelRoom();
    const setModal = useSetModal();
    const router = useRouter();

    const t = useTranslation();
    const dispatchToastMessage = useToastMessageDispatch();

    const [onHoldModalActive, setOnHoldModalActive] = useState(false);

    const visitorRoomId = room.v._id;
    const rid = room._id;
    const uid = useUserId();
    const roomLastMessage = room.lastMessage;

    const getVisitorInfo = useEndpoint('GET', '/v1/livechat/visitors.info');

    const getVisitorEmail = useMutableCallback(async () => {
        if (!visitorRoomId) {
            return;
        }

        const {
            visitor: { visitorEmails },
        } = await getVisitorInfo({ visitorId: visitorRoomId });

        if (visitorEmails?.length && visitorEmails[0].address) {
            return visitorEmails[0].address;
        }
    });

    useEffect(() => {
        if (onHoldModalActive && roomLastMessage?.token) {
            setModal(null);
        }
    }, [roomLastMessage, onHoldModalActive, setModal]);

    const closeModal = useCallback(() => setModal(null), [setModal]);

    const requestTranscript = useEndpoint('POST', '/v1/livechat/transcript/:rid', { rid });

    const handleRequestTranscript = useCallback(
        async (email: string, subject: string) => {
            try {
                await requestTranscript({ email, subject });
                closeModal();
                dispatchToastMessage({
                    type: 'success',
                    message: t('Livechat_email_transcript_has_been_requested'),
                });
            } catch (error) {
                dispatchToastMessage({ type: 'error', message: error });
            }
        },
        [closeModal, dispatchToastMessage, requestTranscript, t],
    );

    const sendTranscriptPDF = useEndpoint('POST', '/v1/omnichannel/:rid/request-transcript', { rid });

    const handleSendTranscriptPDF = useCallback(async () => {
        try {
            await sendTranscriptPDF();
            dispatchToastMessage({
                type: 'success',
                message: t('Livechat_transcript_has_been_requested'),
            });
        } catch (error) {
            dispatchToastMessage({ type: 'error', message: error });
        }
    }, [dispatchToastMessage, sendTranscriptPDF, t]);

    const sendTranscript = useMethod('livechat:sendTranscript');

    const handleSendTranscript = useCallback(
        async (email: string, subject: string, token: string) => {
            try {
                await sendTranscript(token, rid, email, subject);
                closeModal();
            } catch (error) {
                dispatchToastMessage({ type: 'error', message: error });
            }
        },
        [closeModal, dispatchToastMessage, rid, sendTranscript],
    );

    const discardTranscript = useEndpoint('DELETE', '/v1/livechat/transcript/:rid', { rid });

    const handleDiscardTranscript = useCallback(async () => {
        try {
            await discardTranscript();
            dispatchToastMessage({
                type: 'success',
                message: t('Livechat_transcript_request_has_been_canceled'),
            });
            closeModal();
        } catch (error) {
            dispatchToastMessage({ type: 'error', message: error });
        }
    }, [closeModal, discardTranscript, dispatchToastMessage, t]);

    const forwardChat = useEndpoint('POST', '/v1/livechat/room.forward');

    const handleForwardChat = useCallback(
        async (departmentId?: string, userId?: string, comment?: string) => {
            if (departmentId && userId) {
                return;
            }
            const transferData: {
                roomId: string;
                clientAction: boolean;
                comment?: string;
                departmentId?: string;
                userId?: string;
            } = {
                roomId: rid,
                comment,
                clientAction: true,
            };

            if (departmentId) {
                transferData.departmentId = departmentId;
            }
            if (userId) {
                transferData.userId = userId;
            }

            try {
                await forwardChat(transferData);
                dispatchToastMessage({ type: 'success', message: t('Transferred') });
                router.navigate('/home');
                LegacyRoomManager.close(room.t + rid);
                closeModal();
            } catch (error) {
                dispatchToastMessage({ type: 'error', message: error });
            }
        },
        [closeModal, dispatchToastMessage, forwardChat, room.t, rid, router, t],
    );

    const closeChat = useEndpoint('POST', '/v1/livechat/room.closeByUser');

    const handleClose = useCallback(
        async (
            comment?: string,
            tags?: string[],
            preferences?: { omnichannelTranscriptPDF: boolean; omnichannelTranscriptEmail: boolean },
            requestData?: { email: string; subject: string },
        ) => {
            try {
                await closeChat({
                    rid,
                    ...(comment && { comment }),
                    ...(tags && { tags }),
                    ...(preferences?.omnichannelTranscriptPDF && { generateTranscriptPdf: true }),
                    ...(preferences?.omnichannelTranscriptEmail && requestData
                        ? {
                                transcriptEmail: {
                                    sendToVisitor: preferences?.omnichannelTranscriptEmail,
                                    requestData,
                                },
                          }
                        : { transcriptEmail: { sendToVisitor: false } }),
                });
                LivechatInquiry.remove({ rid });
                closeModal();
                dispatchToastMessage({ type: 'success', message: t('Chat_closed_successfully') });
            } catch (error) {
                dispatchToastMessage({ type: 'error', message: error });
            }
        },
        [closeChat, closeModal, dispatchToastMessage, rid, t],
    );

    const returnChatToQueueMutation = useReturnChatToQueueMutation({
        onSuccess: () => {
            LegacyRoomManager.close(room.t + rid);
            router.navigate('/home');
        },
        onError: (error) => {
            dispatchToastMessage({ type: 'error', message: error });
        },
        onSettled: () => {
            closeModal();
        },
    });

    const putChatOnHoldMutation = usePutChatOnHoldMutation({
        onSuccess: () => {
            dispatchToastMessage({ type: 'success', message: t('Chat_On_Hold_Successfully') });
        },
        onError: (error) => {
            dispatchToastMessage({ type: 'error', message: error });
        },
        onSettled: () => {
            closeModal();
        },
    });

    const handleAction = useMutableCallback(async (id: string) => {
        switch (id) {
            case QuickActionsEnum.MoveQueue:
                setModal(
                    <ReturnChatQueueModal
                        onMoveChat={(): void => returnChatToQueueMutation.mutate(rid)}
                        onCancel={(): void => {
                            closeModal();
                        }}
                    />,
                );
                break;
            case QuickActionsEnum.TranscriptPDF:
                handleSendTranscriptPDF();
                break;
            case QuickActionsEnum.TranscriptEmail:
                const visitorEmail = await getVisitorEmail();

                if (!visitorEmail) {
                    dispatchToastMessage({ type: 'error', message: t('Customer_without_registered_email') });
                    break;
                }

                setModal(
                    <TranscriptModal
                        room={room}
                        email={visitorEmail}
                        onRequest={handleRequestTranscript}
                        onSend={handleSendTranscript}
                        onDiscard={handleDiscardTranscript}
                        onCancel={closeModal}
                    />,
                );
                break;
            case QuickActionsEnum.ChatForward:
                setModal(<ForwardChatModal room={room} onForward={handleForwardChat} onCancel={closeModal} />);
                break;
            case QuickActionsEnum.CloseChat:
                const email = await getVisitorEmail();
                setModal(
                    room.departmentId ? (
                        <CloseChatModalData visitorEmail={email} departmentId={room.departmentId} onConfirm={handleClose} onCancel={closeModal} />
                    ) : (
                        <CloseChatModal visitorEmail={email} onConfirm={handleClose} onCancel={closeModal} />
                    ),
                );
                break;
            case QuickActionsEnum.OnHoldChat:
                setModal(
                    <PlaceChatOnHoldModal
                        onOnHoldChat={(): void => putChatOnHoldMutation.mutate(rid)}
                        onCancel={(): void => {
                            closeModal();
                            setOnHoldModalActive(false);
                        }}
                    />,
                );
                setOnHoldModalActive(true);
                break;
            default:
                break;
        }
    });

    const omnichannelRouteConfig = useOmnichannelRouteConfig();

    const manualOnHoldAllowed = useSetting('Livechat_allow_manual_on_hold');

    const hasManagerRole = useRole('livechat-manager');
    const hasMonitorRole = useRole('livechat-monitor');

    const roomOpen = room?.open && (room.u?._id === uid || hasManagerRole || hasMonitorRole) && room?.lastMessage?.t !== 'livechat-close';
    const canMoveQueue = !!omnichannelRouteConfig?.returnQueue && room?.u !== undefined;
    const canForwardGuest = usePermission('transfer-livechat-guest');
    const canSendTranscriptEmail = usePermission('send-omnichannel-chat-transcript');
    const hasLicense = useHasLicenseModule('livechat-enterprise');
    const canSendTranscriptPDF = usePermission('request-pdf-transcript');
    const canCloseRoom = usePermission('close-livechat-room');
    const canCloseOthersRoom = usePermission('close-others-livechat-room');
    const restrictedOnHold = useSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only');
    const canRoomBePlacedOnHold = !room.onHold && room.u;
    const canAgentPlaceOnHold = !room.lastMessage?.token;
    const canPlaceChatOnHold = Boolean(manualOnHoldAllowed && canRoomBePlacedOnHold && (!restrictedOnHold || canAgentPlaceOnHold));
    const isRoomOverMacLimit = useIsRoomOverMacLimit(room);

    const hasPermissionButtons = (id: string): boolean => {
        switch (id) {
            case QuickActionsEnum.MoveQueue:
                return !isRoomOverMacLimit && !!roomOpen && canMoveQueue;
            case QuickActionsEnum.ChatForward:
                return !isRoomOverMacLimit && !!roomOpen && canForwardGuest;
            case QuickActionsEnum.Transcript:
                return !isRoomOverMacLimit && (canSendTranscriptEmail || (hasLicense && canSendTranscriptPDF));
            case QuickActionsEnum.TranscriptEmail:
                return !isRoomOverMacLimit && canSendTranscriptEmail;
            case QuickActionsEnum.TranscriptPDF:
                return hasLicense && !isRoomOverMacLimit && canSendTranscriptPDF;
            case QuickActionsEnum.CloseChat:
                return !!roomOpen && (canCloseRoom || canCloseOthersRoom);
            case QuickActionsEnum.OnHoldChat:
                return !!roomOpen && canPlaceChatOnHold;
            default:
                break;
        }
        return false;
    };

    const quickActions = quickActionHooks
        .map((quickActionHook) => quickActionHook())
        .filter((quickAction): quickAction is QuickActionsActionConfig => !!quickAction)
        .filter((action) => {
            const { options, id } = action;
            if (options) {
                action.options = options.filter(({ id }) => hasPermissionButtons(id));
            }

            return hasPermissionButtons(id);
        })
        .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));

    const actionDefault = useMutableCallback((actionId: string) => {
        handleAction(actionId);
    });

    return { quickActions, actionDefault };
};