atlp-rwanda/hackers-ec-Fe

View on GitHub
src/components/chat/ChatModal.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
A
90%
import { zodResolver } from '@hookform/resolvers/zod';
import { motion } from 'framer-motion';
import { useEffect, useRef } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { io, Socket } from 'socket.io-client';
import { toast } from 'sonner';
import useToken from '../../hooks/useToken';
import { addChat, chats } from '../../redux/features/chatSlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks/hooks';
import fetchInfo from '../../utils/userDetails';
import { chatModalVariants } from '../../utils/variants';
import {
    chatInputType,
    chatInputValidations,
} from '../../validations/chat/chatInputValidations';
import ChatRedirections from '../Redirection/ChatRedirections';
import ChatMessages from './ChatMessages';

const ChatModal = () => {
    const dispatch = useAppDispatch();
    const { chat } = useAppSelector((state) => state.chat);
    const { accessToken } = useToken();
    const socket = useRef<Socket | null>(null);
    const user = fetchInfo();
    const id = user?.id;
    const messagesEndRef = useRef<HTMLDivElement | null>(null);

    const scrollToBottom = () => {
        messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
    };
    useEffect(() => {
        if (accessToken) {
            socket.current = io(`${import.meta.env.VITE_API_APP_ROOT_URL}`, {
                transports: ['websocket'],
                auth: {
                    token: accessToken,
                },
            });

            socket.current.on('connect', () => {
                toast.success('You are live. feel free to chat');
            });

            socket.current.on('chat messages', (msg: []) => {
                dispatch(chats(msg));
                scrollToBottom();
            });

            socket.current.on('new message', (data: []) => {
                dispatch(addChat(data));
                scrollToBottom();
            });

            socket.current.on('disconnect', () => {
                toast.warning('You left the chat. feel free to come back any time');
            });

            return () => {
                if (socket.current) {
                    socket.current.disconnect();
                }
            };
        }
    }, [accessToken, dispatch]);

    const { register, handleSubmit, reset } = useForm<chatInputType>({
        resolver: zodResolver(chatInputValidations),
    });

    const onSubmit: SubmitHandler<chatInputType> = async (data) => {
        if (socket.current) {
            socket.current.emit('send message', { message: data.message });
        }
        reset();
    };

    useEffect(() => {
        scrollToBottom();
    }, [chat]);

    return (
        <>
            {accessToken ? (
                <div className="h-screen w-full fixed inset-0 z-40 ">
                    <motion.div
                        className="chat_modal absolute laptop:right-10 z-50 bg-neutral-white w-full laptop:w-[40%] laptop:h-[70%] desktop:h-[60%] desktop:w-[30%] mobile:h-[100%] tablet:h-[100%] h-[95%] top-16 mobile:top-20 tablet:top-24 laptop:top-32 desktop:top-60 laptop:mt-2 desktop:mt-0 shadow-lg rounded-md overflow-hidden _shadow"
                        variants={chatModalVariants}
                        initial="initial"
                        animate="animate"
                        exit="exit"
                    >
                        <div className="top_chat flex items-center gap-4 bg-primary-lightblue text-neutral-white font-bold p-4 _shadow">
                            <div className="profile w-5 h-5 rounded-full bg-[#0CFF51]"></div>
                            <h1 className="text-xl mobile:text-2xl laptop:text-lg">
                                Shop Trove Live Chat
                            </h1>
                        </div>
                        <div className="chat_messages h-[75%] overflow-auto bg-gray-100 px-5 flex flex-col">
                            {chat?.map((message, index) => (
                                <ChatMessages index={index} message={message} id={id} />
                            ))}
                            <div ref={messagesEndRef} />
                        </div>
                        <div className="add_message bg-gray-200 p-4 _shadow">
                            <form
                                className="flex items-center gap-2 w-full rounded"
                                onSubmit={handleSubmit(onSubmit)}
                            >
                                <input
                                    type="text"
                                    placeholder="Send message"
                                    className="flex-1 p-2 bg-[#EAF0F6] rounded text-xl"
                                    {...register('message')}
                                />
                                <button
                                    type="submit"
                                    className="bg-primary-lightblue px-4 py-1 rounded text-neutral-white text-xl"
                                >
                                    Send
                                </button>
                            </form>
                        </div>
                    </motion.div>
                </div>
            ) : (
                <ChatRedirections />
            )}
        </>
    );
};

export default ChatModal;