Skip to content

Commit

Permalink
🧍🏽 Search feature and UI refactor work in messages page (#1149)
Browse files Browse the repository at this point in the history
* Search feature and UI refactor work in messages page

* code format

* message file format
  • Loading branch information
hv2308 authored Jan 3, 2025
1 parent 0085379 commit 2a9e4e8
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 28 deletions.
4 changes: 3 additions & 1 deletion backend/api/views/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,13 @@ def get_sidebar(user_id):
"otherUser": otherUserObj,
"latestMessage": json.loads(message.to_json()),
}

allMessages = [json.loads(message.to_json()) for message in sentMessages]

contacts.append(sidebarObject)
sidebarContacts.add(otherId)

return create_response(data={"data": contacts}, status=200, message="res")
return create_response(data={"data": contacts, "allMessages": allMessages,}, status=200, message="res")
except Exception as e:
logger.info(e)
return create_response(status=422, message=str(e))
Expand Down
4 changes: 3 additions & 1 deletion frontend/public/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,9 @@
"sendMessagePlaceholder": "Send a message",
"sidebarTitle": "My Messages",
"reply":"Reply",
"startConversation": "Start a conversation today!"
"startConversation": "Start a conversation today!",
"sent": "Sent",
"search": "Search"
},
"navHeader": {
"editProfile": "Edit Profile",
Expand Down
19 changes: 14 additions & 5 deletions frontend/src/components/MessageCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ function MessageCard(props) {
background: ${colorPrimaryBg};
}
`;

const descriptionClass = css`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
display: block;
`;

return (
<Card
onClick={openMessage}
Expand All @@ -111,7 +120,7 @@ function MessageCard(props) {
${active && activeCardStyle}
`}
>
{accountData ? (
{otherUser ? (
<div
className={
active &&
Expand All @@ -123,11 +132,11 @@ function MessageCard(props) {
}
>
<Meta
avatar={<Avatar src={accountData.image?.url} />}
title={
accountData.name ? accountData.name : accountData.organization
avatar={<Avatar src={otherUser.image} />}
title={otherUser.name ? otherUser.name : accountData.organization}
description={
<span className={descriptionClass}>{latestMessage.body}</span>
}
description={latestMessage.body}
/>
</div>
) : null}
Expand Down
46 changes: 39 additions & 7 deletions frontend/src/components/MessagesChatArea.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import { css } from "@emotion/css";

function MessagesChatArea(props) {
const {
token: { colorPrimaryBg, colorPrimary },
token: { colorPrimaryBg },
} = theme.useToken();
const { t } = useTranslation();
const queryParameters = new URLSearchParams(window.location.search);
const messageId = queryParameters.get("message_id");
const { socket } = props;
const { TextArea } = Input;
const { profileId, isMentee, isMentor, isPartner } = useAuth();
Expand Down Expand Up @@ -62,6 +64,18 @@ function MessagesChatArea(props) {
});
}
};

function scrollToElement(id) {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "center",
});
}
}

useEffect(() => {
async function fetchAccount() {
var account = null;
Expand Down Expand Up @@ -135,9 +149,15 @@ function MessagesChatArea(props) {
}
fetchAccount();
}, [updateContent, otherId, messages]);

useEffect(() => {
scrollToBottom();
}, [loading, messages]);
if (messageId) {
if (!messages?.length) return;
scrollToElement(messageId);
} else {
scrollToBottom();
}
}, [loading, messages, messageId]);

async function getAppointments() {
const mentorID = profileId;
Expand Down Expand Up @@ -354,7 +374,15 @@ function MessagesChatArea(props) {
};

const HtmlContent = ({ content }) => {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
return (
<div
style={{
wordBreak: isMobile ? "break-word" : "normal",
fontSize: "15px",
}}
dangerouslySetInnerHTML={{ __html: content }}
/>
);
};

return (
Expand Down Expand Up @@ -436,12 +464,13 @@ function MessagesChatArea(props) {
<div className="conversation-content">
<Spin spinning={loading}>
{accountData &&
messages.map((block) => {
messages.map((block, index) => {
return (
<div
className={`chatRight__items you-${
block.sender_id.$oid === profileId ? "sent" : "received"
}`}
id={block?._id?.$oid || index}
>
<div
className={`chatRight__inner message-area ${
Expand Down Expand Up @@ -568,13 +597,16 @@ function MessagesChatArea(props) {
onClick={sendMessage}
className={css`
margin-left: 0.5em;
padding: 0.5em;
`}
shape="circle"
shape="default"
type="primary"
ref={buttonRef}
icon={<SendOutlined rotate={315} />}
size={48}
/>
>
{t("messages.sent")}
</Button>
</>
)}
</div>
Expand Down
31 changes: 29 additions & 2 deletions frontend/src/components/MessagesSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { withRouter } from "react-router-dom";
import { Divider, Input, Layout } from "antd";
import MessageCard from "./MessageCard";
import { useTranslation } from "react-i18next";
import { SearchOutlined } from "@ant-design/icons";
import { css } from "@emotion/css";
import SearchMessageCard from "./SearchMessageCard";

function MessagesSidebar(props) {
const { t } = useTranslation();
Expand Down Expand Up @@ -75,13 +78,37 @@ function MessagesSidebar(props) {
<div className="messages-sidebar-header">
<h1>{t("messages.sidebarTitle")}</h1>
</div>
<div
className={css`
padding: 0 20px;
margin-bottom: 10px;
`}
>
<Input
placeholder={t("messages.search")}
prefix={<SearchOutlined />}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<Divider className="header-divider" orientation="left"></Divider>
<div className="messages-sidebar" style={{ paddingTop: "1em" }}>
{side_data &&
{searchQuery && (
<SearchMessageCard
activeMessageId={activeMessageId}
messages={props?.allMessages}
searchQuery={searchQuery}
side_data={side_data}
/>
)}
{!searchQuery &&
side_data &&
side_data.length > 0 &&
side_data.map((chat) => {
if (
chat.otherId.toLowerCase().includes(searchQuery.toLowerCase())
chat.otherUser.name
.toLowerCase()
.includes(searchQuery.toLowerCase())
) {
if (chat.otherId === activeMessageId) {
return (
Expand Down
152 changes: 152 additions & 0 deletions frontend/src/components/SearchMessageCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { css } from "@emotion/css";
import { Avatar, Card, Tabs, theme } from "antd";
import Meta from "antd/es/card/Meta";
import TabPane from "antd/es/tabs/TabPane";
import { setActiveMessageId } from "features/messagesSlice";
import { updateNotificationsCount } from "features/notificationsSlice";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useMediaQuery } from "react-responsive";
import { useHistory } from "react-router";
import MessageCard from "./MessageCard";

const SearchMessageCard = ({
messages,
searchQuery,
side_data,
activeMessageId,
}) => {
const [activeTab, setActiveTab] = useState("message");
const {
token: { colorPrimaryBorder, colorBorderSecondary },
} = theme.useToken();
const history = useHistory();
const dispatch = useDispatch();
const thisUserId = useSelector((state) => state.user.user?._id?.$oid);
const isMobile = useMediaQuery({ query: `(max-width: 761px)` });

const findUserInfo = (id) => {
const user = side_data.find((item) => item.otherId === id);

return user;
};

const filteredMessages = searchQuery
? messages.filter((message) =>
message.body.toLowerCase().includes(searchQuery.toLowerCase())
)
: messages;

const highlightText = (text, query) => {
if (!query) return text;
const regex = new RegExp(`(${query})`, "gi");
return text.split(regex).map((part, index) =>
part.toLowerCase() === query.toLowerCase() ? (
<span key={index} style={{ backgroundColor: "yellow" }}>
{part}
</span>
) : (
part
)
);
};

const openMessage = (message) => {
dispatch(
updateNotificationsCount({
recipient: thisUserId,
sender: message.recipient_id?.$oid,
})
);
dispatch(setActiveMessageId({ activeMessageId: thisUserId }));
history.push(
`/messages/${message.recipient_id?.$oid}?user_type=${
findUserInfo(message.recipient_id?.$oid)?.otherUser?.user_type
}&message_id=${message._id?.$oid}`
);
if (isMobile) {
var sidebar = document.getElementsByClassName("ant-layout-sider");
if (sidebar.length > 0) {
sidebar[0].style.display = "none";
}
var message_container = document.getElementsByClassName(
"conversation-container"
);
if (message_container.length > 0) {
message_container[0].style.display = "flex";
}
}
};

const descriptionClass = css`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
display: block;
`;

return (
<Tabs centered activeKey={activeTab} onChange={setActiveTab}>
<TabPane tab="User" key="user">
{side_data.map((chat) => {
if (
chat.otherUser.name
.toLowerCase()
.includes(searchQuery.toLowerCase())
)
return (
<MessageCard
key={chat.otherId}
chat={chat}
active={chat.otherId === activeMessageId}
/>
);
})}
</TabPane>
<TabPane tab="Message" key="message">
{filteredMessages.map((message) => {
const userInfo = findUserInfo(message.recipient_id?.$oid)?.otherUser;
return (
<Card
key={message.id}
onClick={() => openMessage(message)}
className={css`
width: 90%;
margin-left: auto;
margin-right: auto;
margin-bottom: 3%;
border: 1px solid "#e8e8e8";
box-sizing: border-box;
border-radius: 7px;
:hover {
background-color: ${colorBorderSecondary};
border-color: ${colorPrimaryBorder};
cursor: pointer;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
`}
>
<div>
<Meta
avatar={<Avatar src={userInfo?.image} />}
title={
userInfo?.name ? userInfo?.name : userInfo?.organization
}
description={
<span className={descriptionClass}>
{highlightText(message.body, searchQuery)}
</span>
}
/>
</div>
</Card>
);
})}
</TabPane>
</Tabs>
);
};

export default SearchMessageCard;
Loading

0 comments on commit 2a9e4e8

Please sign in to comment.