w-aura/src/components/ChatsPath/pages/ExpertChat/index.tsx
2025-04-18 18:54:01 +00:00

398 lines
12 KiB
TypeScript

import { useNavigate } from "react-router-dom";
import styles from "./styles.module.scss";
import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react";
import Loader, { LoaderColor } from "@/components/Loader";
import ChatHeader from "./components/ChatHeader";
import InputMessage from "./components/InputMessage";
import routes from "@/routes";
import Message from "./components/Message";
import LoaderDots from "./components/LoaderDots";
import StartInfo from "./components/StartInfo";
import RefillCreditsModal from "./components/RefillCreditsModal";
import useChatSocket from "@/hooks/chatsSocket/useChatsSocket";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import BottomModal from "../../components/BottomModal";
import OutOfCreditsModal from "./components/OutOfCreditsModal";
import RefillProductsModal from "./components/RefillProductsModal";
import { Products, useApi, useApiCall } from "@/api";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { useAuth } from "@/auth";
import { createSinglePayment } from "@/services/singlePayment";
import { IMessage } from "@/api/resources/ChatMessages";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import { usePayment } from "@/hooks/payment/nmi/usePayment";
const returnUrl = `${window.location.protocol}//${window.location.host
}${routes.client.chatsExpert()}`;
const placementKey = EPlacementKeys["aura.placement.chat"];
function ExpertChat() {
const { translate } = useTranslations(ELocalesPlacement.Chats);
const api = useApi();
const dispatch = useDispatch();
const navigate = useNavigate();
const assistant = useSelector(selectors.selectCurrentAssistant);
const chatId = useSelector(selectors.selectCurrentChatId);
const userId = useSelector(selectors.selectUserId);
const [messageText, setMessageText] = useState("");
const [textareaRows, setTextareaRows] = useState(1);
const messagesEndRef = useRef<HTMLDivElement>(null);
const [isShowRefillCreditsModal, setIsShowRefillCreditsModal] =
useState(false);
const [isShowOutOfCreditsModal, setIsShowOutOfCreditsModal] = useState(false);
const [isShowRefillProductsModal, setIsShowRefillProductsModal] =
useState(false);
const {
isLoading,
isLoadingSelfMessage,
isLoadingAdvisorMessage,
messages,
isAvailableChatting,
messagesAfterEnd,
initialBalance,
sendMessage,
readMessage,
} = useChatSocket(userId, chatId);
// Payment
const { user: userFromStore } = useAuth();
const tokenFromStore = useSelector(selectors.selectToken);
const [isLoadingPayment, setIsLoadingPayment] = useState(false);
const [isError, setIsError] = useState(false);
const currentProduct = useSelector(selectors.selectActiveProduct);
const setCurrentProduct = (product: IPaywallProduct) => {
dispatch(actions.payment.update({ activeProduct: product }));
};
const {
error,
isPaymentSuccess,
showCreditCardForm,
} = usePayment({
placementKey,
activeProduct: currentProduct!,
paymentFormType: "lightbox"
});
const isPayedFirstPurchase = useSelector(
selectors.selectIsPayedFirstPurchase
);
const checkIsPayedFirstPurchase = useCallback(async () => {
if (isPayedFirstPurchase) return;
const isPayed = await api.checkProductPurchased({
token: tokenFromStore,
productKey: "credits.100",
});
if (isPayed && "active" in isPayed && isPayed.active) {
dispatch(actions.chat.updateIsPayedFirstPurchase(true));
}
return isPayed;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
api,
dispatch,
isPayedFirstPurchase,
tokenFromStore,
isAvailableChatting,
]);
const { data: isPayedFirstPurchaseResponse } = useApiCall<
Products.ResponseGet | undefined
>(checkIsPayedFirstPurchase);
const { products } = usePaywall({
placementKey,
});
const scrollToBottom = () => {
setTimeout(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, 100);
};
useEffect(() => {
if (!isLoading && !!messages.length) scrollToBottom();
}, [messages, isLoading]);
const handleChangeMessageText = (e: ChangeEvent<HTMLTextAreaElement>) => {
const { scrollHeight, clientHeight, value } = e.target;
if (
scrollHeight > clientHeight &&
textareaRows < 5 &&
value.length > messageText.length
) {
setTextareaRows((prev) => prev + 1);
}
if (
scrollHeight === clientHeight &&
textareaRows > 1 &&
value.length < messageText.length
) {
setTextareaRows((prev) => prev - 1);
}
setMessageText(e.target.value);
};
const getIsSelfMessage = (role: string) => role === "user";
const deleteDataFromMessage = (messageText: string) => {
const splittedText = messageText?.split("#");
if (splittedText?.length > 2) {
return splittedText?.slice(2).join("#");
}
return messageText;
};
const endChat = () => {
navigate(routes.client.chatsCategories());
};
const handleRefillModalClickButton = () => {
createPayment("credits.100");
};
const handleOutOfCreditsModalClickButton = () => {
createPayment("credits.80");
};
const handleRefillProductsModalClickButton = (productKey: string) => {
createPayment(productKey);
};
const handleSendMessage = (messageText: string) => {
if (!isAvailableChatting) {
showModals();
}
setMessageText("");
sendMessage(messageText);
};
const closeModals = useCallback(() => {
setIsShowRefillProductsModal(false);
setIsShowOutOfCreditsModal(false);
setIsShowRefillCreditsModal(false);
}, []);
const createPayment = useCallback(
async (productKey: string) => {
try {
if (!userFromStore || !productKey.length) return;
const currentProduct =
products?.find((product) => product.key === productKey) || null;
if (!currentProduct) return;
setCurrentProduct(currentProduct);
setIsLoadingPayment(true);
const isPaymentMethodExist = await api.getPaymentMethods({ token: tokenFromStore });
if (isPaymentMethodExist.status === "error") {
return showCreditCardForm();
}
const { _id, key } = currentProduct;
const paymentInfo = {
productId: _id,
key,
};
const paymentIntent = await createSinglePayment(
userFromStore,
paymentInfo,
tokenFromStore,
userFromStore.email,
userFromStore.profile.full_name,
userFromStore.profile.birthday,
returnUrl,
api
);
// setPaymentIntent(paymentIntent);
if ("payment" in paymentIntent) {
if (paymentIntent.payment.status === "paid") return closeModals();
return setIsError(true);
}
} catch (error) {
console.log(error);
setIsError(true);
} finally {
setIsLoadingPayment(false);
}
},
[api, closeModals, products, tokenFromStore, userFromStore]
);
const showModals = useCallback(() => {
if (isPayedFirstPurchase) {
return setIsShowOutOfCreditsModal(true);
}
if (
isPayedFirstPurchase ||
!isPayedFirstPurchaseResponse ||
!("active" in isPayedFirstPurchaseResponse) ||
!isPayedFirstPurchaseResponse
) {
return setIsShowOutOfCreditsModal(true);
}
return setIsShowRefillCreditsModal(true);
}, [isPayedFirstPurchase, isPayedFirstPurchaseResponse]);
useEffect(() => {
if (isAvailableChatting && !isLoading) {
closeModals();
}
}, [closeModals, isAvailableChatting, isLoading]);
useEffect(() => {
if (initialBalance !== null && !isLoading && !initialBalance) {
showModals();
}
}, [initialBalance, isLoading, showModals]);
const isBlurMessage = (message: IMessage) => {
return (
!!messagesAfterEnd.find((item) => item.id === message.id) &&
message.role === "assistant"
);
};
const onPaymentError = () => {
return setIsError(true);
}
const onPaymentSuccess = () => {
setIsLoadingPayment(false);
return closeModals();
}
useEffect(() => {
if (error) {
onPaymentError();
}
}, [error])
useEffect(() => {
if (isPaymentSuccess) {
onPaymentSuccess();
}
}, [isPaymentSuccess])
return (
<section className={`${styles.page} page`}>
{isLoading && (
<Loader color={LoaderColor.Red} className={styles.loader} />
)}
{!isLoading && (
<ChatHeader
name={assistant?.name || ""}
avatar={assistant?.image || ""}
classNameContainer={styles["header-container"]}
clickBackButton={endChat}
isTimerGoing={isAvailableChatting}
hasBackButton={false}
/>
)}
{!isLoading && !!messages.length && (
<div className={styles["messages-container"]}>
{messages.map((message) => (
<Message
avatar={assistant?.image || ""}
text={deleteDataFromMessage(message.text)}
isVisible={deleteDataFromMessage(message.text) !== " HI"}
advisorName={assistant?.name || ""}
backgroundTextColor={
getIsSelfMessage(message.role) ? "#0080ff" : "#c9c9c9"
}
textColor={getIsSelfMessage(message.role) ? "#fff" : "#000"}
isSelf={getIsSelfMessage(message.role)}
key={message.id}
isRead={message.isRead}
readMessage={readMessage}
messageId={message.id}
isBlur={isBlurMessage(message)}
onClickBlur={() => showModals()}
/>
))}
{isLoadingAdvisorMessage && !isLoadingSelfMessage && (
<Message
avatar={assistant?.image || ""}
text={<LoaderDots />}
isSelf={false}
backgroundTextColor={"#c9c9c9"}
textColor={"#000"}
/>
)}
</div>
)}
{!messages.length && !isLoading && <StartInfo />}
<div ref={messagesEndRef} />
{!isLoading && (
<InputMessage
placeholder={translate("/expert.input_label")}
messageText={messageText}
textareaRows={textareaRows}
disabledTextArea={isLoadingAdvisorMessage || isLoadingSelfMessage}
disabledButton={!messageText.length}
classNameContainer={styles["input-container"]}
handleChangeMessageText={handleChangeMessageText}
isLoading={isLoadingSelfMessage}
submitForm={handleSendMessage}
description={translate("/expert.cost", {
price: assistant?.price || 0,
})}
/>
)}
{!isLoading && (
<>
{isShowRefillCreditsModal && (
<BottomModal handleClose={() => setIsShowRefillCreditsModal(false)}>
<RefillCreditsModal
isError={isError}
isLoading={isLoadingPayment}
products={products}
handleClickButton={handleRefillModalClickButton}
/>
</BottomModal>
)}
{isShowOutOfCreditsModal && (
<BottomModal handleClose={() => setIsShowOutOfCreditsModal(false)}>
<OutOfCreditsModal
isError={isError}
isLoading={isLoadingPayment}
products={products}
handleClickButton={handleOutOfCreditsModalClickButton}
handleClickNotWant={endChat}
timerLeft={() => {
if (isLoadingPayment) return;
setIsShowOutOfCreditsModal(false);
setIsShowRefillProductsModal(true);
}}
/>
</BottomModal>
)}
{isShowRefillProductsModal && (
<BottomModal
handleClose={() => setIsShowRefillProductsModal(false)}
>
<RefillProductsModal
isError={isError}
isLoading={isLoadingPayment}
products={products}
handleClickButton={handleRefillProductsModalClickButton}
/>
</BottomModal>
)}
</>
)}
</section>
);
}
export default ExpertChat;