Files
web/components/PagesComponent/Chat/ChatMessages.jsx
Husanjonazamov 64af77101f classify web
2026-02-24 12:52:49 +05:00

344 lines
12 KiB
JavaScript

import { userSignUpData } from "@/redux/reducer/authSlice";
import {
formatChatMessageTime,
formatMessageDate,
formatPriceAbbreviated,
t,
} from "@/utils";
import { getMessagesApi } from "@/utils/api";
import { Fragment, useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
import { Skeleton } from "@/components/ui/skeleton";
import { Loader2, ChevronUp } from "lucide-react";
import dynamic from "next/dynamic";
const SendMessage = dynamic(() => import("./SendMessage"), { ssr: false });
import GiveReview from "./GiveReview";
import { getNotification } from "@/redux/reducer/globalStateSlice";
import CustomImage from "@/components/Common/CustomImage";
import { cn } from "@/lib/utils";
// Skeleton component for chat messages
const ChatMessagesSkeleton = () => {
return (
<div className="flex flex-col gap-4 w-full">
{/* Skeleton for date separator */}
{/* Received message skeletons */}
<div className="flex flex-col gap-1 w-[65%] max-w-[80%]">
<Skeleton className="h-16 w-full rounded-md" />
<Skeleton className="h-3 w-[30%] rounded-md" />
</div>
{/* Sent message skeletons */}
<div className="flex flex-col gap-1 w-[70%] max-w-[80%] self-end">
<Skeleton className="h-10 w-full rounded-md" />
<Skeleton className="h-3 w-[30%] self-end rounded-md" />
</div>
{/* Image message skeleton */}
<div className="flex flex-col gap-1 w-[50%] max-w-[80%]">
<Skeleton className="h-32 w-full rounded-md" />
<Skeleton className="h-3 w-[30%] rounded-md" />
</div>
{/* Audio message skeleton */}
<div className="flex flex-col gap-1 w-[60%] max-w-[80%] self-end">
<Skeleton className="h-12 w-full rounded-md" />
<Skeleton className="h-3 w-[30%] self-end rounded-md" />
</div>
{/* Another message skeleton */}
<div className="flex flex-col gap-1 w-[45%] max-w-[80%]">
<Skeleton className="h-14 w-full rounded-md" />
<Skeleton className="h-3 w-[30%] rounded-md" />
</div>
<div className="flex flex-col gap-1 w-[60%] max-w-[80%] self-end">
<Skeleton className="h-12 w-full rounded-md" />
<Skeleton className="h-3 w-[30%] self-end rounded-md" />
</div>
{/* Another message skeleton */}
<div className="flex flex-col gap-1 w-[45%] max-w-[80%]">
<Skeleton className="h-14 w-full rounded-md" />
<Skeleton className="h-3 w-[30%] rounded-md" />
</div>
</div>
);
};
const renderMessageContent = (message, isCurrentUser) => {
const baseTextClass = isCurrentUser
? "text-white bg-primary p-2 rounded-md w-fit"
: "text-black bg-border p-2 rounded-md w-fit";
const audioStyles = isCurrentUser ? "border-primary" : "border-border";
switch (message.message_type) {
case "audio":
return (
<audio
src={message.audio}
controls
className={`w-full sm:w-[70%] ${
isCurrentUser ? "self-end" : "self-start"
} rounded-md border-2 ${audioStyles}`}
controlsList="nodownload"
type="audio/mpeg"
preload="metadata"
/>
);
case "file":
return (
<div className={`${baseTextClass}`}>
<CustomImage
src={message.file}
alt="Chat Image"
className="rounded-md w-auto h-auto max-h-[250px] max-w-[250px] object-contain"
width={200}
height={200}
/>
</div>
);
case "file_and_text":
return (
<div className={`${baseTextClass} flex flex-col gap-2`}>
<CustomImage
src={message.file}
alt="Chat Image"
className="rounded-md w-auto h-auto max-h-[250px] max-w-[250px] object-contain"
width={200}
height={200}
/>
<div className="border-white/20">{message.message}</div>
</div>
);
default:
return (
<p
className={`${baseTextClass} whitespace-pre-wrap ${
isCurrentUser ? "self-end" : "self-start"
}`}
>
{message?.message}
</p>
);
}
};
const ChatMessages = ({
selectedChatDetails,
isSelling,
setSelectedChatDetails,
setBuyer,
chatId,
}) => {
const notification = useSelector(getNotification);
const [chatMessages, setChatMessages] = useState([]);
const [currentMessagesPage, setCurrentMessagesPage] = useState(1);
const [hasMoreChatMessages, setHasMoreChatMessages] = useState(false);
const [isLoadPrevMesg, setIsLoadPrevMesg] = useState(false);
const [IsLoading, setIsLoading] = useState(false);
const [showReviewDialog, setShowReviewDialog] = useState(false);
const lastMessageDate = useRef(null);
const isAskForReview =
!isSelling &&
selectedChatDetails?.item?.status === "sold out" &&
!selectedChatDetails?.item?.review &&
Number(selectedChatDetails?.item?.sold_to) ===
Number(selectedChatDetails?.buyer_id);
const user = useSelector(userSignUpData);
const userId = user?.id;
useEffect(() => {
if (selectedChatDetails?.id) {
fetchChatMessgaes(1);
}
}, [selectedChatDetails?.id]);
useEffect(() => {
if (
notification?.type === "chat" &&
Number(notification?.item_offer_id) === Number(chatId) &&
(notification?.user_type === "Seller" ? !isSelling : isSelling)
) {
const newMessage = {
message_type: notification?.message_type_temp,
message: notification?.message,
sender_id: Number(notification?.sender_id),
created_at: notification?.created_at,
audio: notification?.audio,
file: notification?.file,
id: Number(notification?.id),
item_offer_id: Number(notification?.item_offer_id),
updated_at: notification?.updated_at,
};
setChatMessages((prev) => [...prev, newMessage]);
}
}, [notification]);
const fetchChatMessgaes = async (page) => {
try {
page > 1 ? setIsLoadPrevMesg(true) : setIsLoading(true);
const response = await getMessagesApi.chatMessages({
item_offer_id: selectedChatDetails?.id,
page,
});
if (response?.data?.error === false) {
const currentPage = Number(response?.data?.data?.current_page);
const lastPage = Number(response?.data?.data?.last_page);
const hasMoreChatMessages = currentPage < lastPage;
const chatMessages = (response?.data?.data?.data).reverse();
setCurrentMessagesPage(currentPage);
setHasMoreChatMessages(hasMoreChatMessages);
page > 1
? setChatMessages((prev) => [...chatMessages, ...prev])
: setChatMessages(chatMessages);
}
} catch (error) {
console.log(error);
} finally {
setIsLoadPrevMesg(false);
setIsLoading(false);
}
};
return (
<>
<div className="flex-1 overflow-y-auto bg-muted p-4 flex flex-col gap-2.5 relative">
{IsLoading ? (
<ChatMessagesSkeleton />
) : (
<>
{/* Show review dialog if open */}
{showReviewDialog && (
<div className="absolute inset-0 z-20 flex items-center justify-center bg-black/20 p-4">
<div className="w-full max-w-md">
<GiveReview
itemId={selectedChatDetails?.item_id}
sellerId={selectedChatDetails?.seller_id}
onClose={() => setShowReviewDialog(false)}
onSuccess={handleReviewSuccess}
/>
</div>
</div>
)}
{/* button to load previous messages */}
{hasMoreChatMessages && !IsLoading && (
<div className="absolute top-3 left-0 right-0 z-10 flex justify-center pb-2">
<button
onClick={() => fetchChatMessgaes(currentMessagesPage + 1)}
disabled={isLoadPrevMesg}
className="text-primary text-sm font-medium px-3 py-1.5 bg-white/90 rounded-full shadow-md hover:bg-white flex items-center gap-1.5"
>
{isLoadPrevMesg ? (
<>
<Loader2 className="w-3.5 h-3.5 animate-spin" />
{t("loading")}
</>
) : (
<>
<ChevronUp className="w-4 h-4" />
{t("loadPreviousMessages")}
</>
)}
</button>
</div>
)}
{/* offer price */}
{!hasMoreChatMessages &&
selectedChatDetails?.amount > 0 &&
(() => {
const isSeller = isSelling;
const containerClasses = `flex flex-col gap-1 rounded-md p-2 w-fit ${
isSeller ? "bg-border" : "bg-primary text-white self-end"
}`;
const label = isSeller ? t("offer") : t("yourOffer");
return (
<div className={containerClasses}>
<p className="text-sm">{label}</p>
<span className="text-xl font-medium">
{selectedChatDetails.formatted_amount}
</span>
</div>
);
})()}
{/* chat messages */}
{chatMessages &&
chatMessages.length > 0 &&
chatMessages.map((message) => {
const messageDate = formatMessageDate(message.created_at);
const showDateSeparator =
messageDate !== lastMessageDate.current;
if (showDateSeparator) {
lastMessageDate.current = messageDate;
}
return (
<Fragment key={message?.id}>
{showDateSeparator && (
<p className="text-xs bg-[#f1f1f1] py-1 px-2 rounded-lg text-muted-foreground my-5 mx-auto">
{messageDate}
</p>
)}
{message.sender_id === userId ? (
<div
className={cn(
"flex flex-col gap-1 max-w-[80%] self-end",
message.message_type === "audio" && "w-full"
)}
key={message?.id}
>
{renderMessageContent(message, true)}
<p className="text-xs text-muted-foreground ltr:text-right rtl:text-left">
{formatChatMessageTime(message?.created_at)}
</p>
</div>
) : (
<div
className={cn(
"flex flex-col gap-1 max-w-[80%]",
message.message_type === "audio" && "w-full"
)}
key={message?.id}
>
{renderMessageContent(message, false)}
<p className="text-xs text-muted-foreground ltr:text-left rtl:text-right">
{formatChatMessageTime(message?.created_at)}
</p>
</div>
)}
</Fragment>
);
})}
</>
)}
</div>
{isAskForReview && (
<GiveReview
key={`review-${selectedChatDetails?.id}`}
itemId={selectedChatDetails?.item_id}
setSelectedChatDetails={setSelectedChatDetails}
setBuyer={setBuyer}
/>
)}
<SendMessage
key={`send-${selectedChatDetails?.id}`}
selectedChatDetails={selectedChatDetails}
setChatMessages={setChatMessages}
/>
</>
);
};
export default ChatMessages;